openhands-sdk 1.49.5__tar.gz → 1.49.6__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 (309) hide show
  1. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/PKG-INFO +1 -1
  2. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/local_conversation.py +31 -0
  3. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/executor.py +4 -3
  4. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/__init__.py +10 -0
  5. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm.py +150 -0
  6. openhands_sdk-1.49.6/openhands/sdk/llm/meta_profile_store.py +274 -0
  7. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_features.py +0 -1
  8. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/verified_models.py +7 -3
  9. openhands_sdk-1.49.6/openhands/sdk/mcp/oauth.py +85 -0
  10. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/utils.py +3 -2
  11. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/api_models.py +9 -0
  12. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/model.py +66 -1
  13. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/__init__.py +11 -0
  14. openhands_sdk-1.49.6/openhands/sdk/tool/builtins/classify_and_switch_llm.py +503 -0
  15. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/PKG-INFO +1 -1
  16. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/SOURCES.txt +6 -0
  17. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/pyproject.toml +1 -1
  18. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/__init__.py +0 -0
  19. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/__init__.py +0 -0
  20. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_agent.py +0 -0
  21. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  22. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_models.py +0 -0
  23. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_tracing.py +0 -0
  24. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/agent.py +0 -0
  25. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/base.py +0 -0
  26. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/critic_mixin.py +0 -0
  27. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/parallel_executor.py +0 -0
  28. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/response_dispatch.py +0 -0
  29. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/stream_context.py +0 -0
  30. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/utils.py +0 -0
  31. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/banner.py +0 -0
  32. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/__init__.py +0 -0
  33. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/agent_context.py +0 -0
  34. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/__init__.py +0 -0
  35. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/base.py +0 -0
  36. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  37. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  38. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  39. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  40. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/utils.py +0 -0
  41. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/memory.py +0 -0
  42. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/__init__.py +0 -0
  43. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/presets.py +0 -0
  44. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/prompt.py +0 -0
  45. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/registry.py +0 -0
  46. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/section.py +0 -0
  47. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  48. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  49. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  50. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/static.py +0 -0
  51. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  52. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  53. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/__init__.py +0 -0
  54. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  55. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/__init__.py +0 -0
  56. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/base.py +0 -0
  57. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  58. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  59. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  60. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  61. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/view.py +0 -0
  62. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/__init__.py +0 -0
  63. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/base.py +0 -0
  64. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/cancellation.py +0 -0
  65. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/conversation.py +0 -0
  66. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/conversation_stats.py +0 -0
  67. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/event_store.py +0 -0
  68. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/events_list_base.py +0 -0
  69. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/exceptions.py +0 -0
  70. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/fifo_lock.py +0 -0
  71. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/__init__.py +0 -0
  72. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/controller.py +0 -0
  73. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/judge.py +0 -0
  74. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/prompts.py +0 -0
  75. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/runner.py +0 -0
  76. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/__init__.py +0 -0
  77. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
  78. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/persistence_const.py +0 -0
  79. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/request.py +0 -0
  80. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  81. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/response_utils.py +0 -0
  82. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/secret_registry.py +0 -0
  83. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/state.py +0 -0
  84. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/stuck_detector.py +0 -0
  85. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/title_utils.py +0 -0
  86. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/types.py +0 -0
  87. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  88. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/base.py +0 -0
  89. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/default.py +0 -0
  90. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/credential.py +0 -0
  91. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/__init__.py +0 -0
  92. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/base.py +0 -0
  93. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/__init__.py +0 -0
  94. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  95. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  96. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  97. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/client.py +0 -0
  98. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/critic.py +0 -0
  99. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  100. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  101. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  102. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/result.py +0 -0
  103. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/__init__.py +0 -0
  104. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/acp_tool_call.py +0 -0
  105. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/base.py +0 -0
  106. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/condenser.py +0 -0
  107. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/conversation_error.py +0 -0
  108. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/conversation_state.py +0 -0
  109. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/error_classification.py +0 -0
  110. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/hook_execution.py +0 -0
  111. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_completion_log.py +0 -0
  112. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  113. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/action.py +0 -0
  114. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/message.py +0 -0
  115. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  116. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  117. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/system.py +0 -0
  118. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/resume_transcript.py +0 -0
  119. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/streaming_delta.py +0 -0
  120. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/token.py +0 -0
  121. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/types.py +0 -0
  122. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/user_action.py +0 -0
  123. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/__init__.py +0 -0
  124. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/fetch.py +0 -0
  125. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/__init__.py +0 -0
  126. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/info.py +0 -0
  127. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/interface.py +0 -0
  128. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/manager.py +0 -0
  129. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/metadata.py +0 -0
  130. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/utils.py +0 -0
  131. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/cached_repo.py +0 -0
  132. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/exceptions.py +0 -0
  133. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/git_changes.py +0 -0
  134. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/git_commits.py +0 -0
  135. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/git_diff.py +0 -0
  136. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/models.py +0 -0
  137. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/utils.py +0 -0
  138. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/__init__.py +0 -0
  139. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/config.py +0 -0
  140. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  141. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/manager.py +0 -0
  142. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/types.py +0 -0
  143. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/__init__.py +0 -0
  144. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/base.py +0 -0
  145. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/cache.py +0 -0
  146. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/local.py +0 -0
  147. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/memory.py +0 -0
  148. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/__init__.py +0 -0
  149. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/credentials.py +0 -0
  150. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/openai.py +0 -0
  151. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/cleanup_profile.py +0 -0
  152. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  153. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  154. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  155. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/types.py +0 -0
  156. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/fallback_strategy.py +0 -0
  157. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_profile_store.py +0 -0
  158. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_registry.py +0 -0
  159. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_response.py +0 -0
  160. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/message.py +0 -0
  161. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  162. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  163. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  164. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/__init__.py +0 -0
  165. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/chat_options.py +0 -0
  166. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/common.py +0 -0
  167. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/responses_options.py +0 -0
  168. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/provider_connection_store.py +0 -0
  169. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/__init__.py +0 -0
  170. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/base.py +0 -0
  171. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  172. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/impl/random.py +0 -0
  173. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/streaming.py +0 -0
  174. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/image_inline.py +0 -0
  175. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/image_resize.py +0 -0
  176. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  177. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/metrics.py +0 -0
  178. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_info.py +0 -0
  179. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  180. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  181. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  182. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  183. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  184. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  185. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  186. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/telemetry.py +0 -0
  187. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  188. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  189. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/logger/__init__.py +0 -0
  190. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/logger/logger.py +0 -0
  191. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/logger/rolling.py +0 -0
  192. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/__init__.py +0 -0
  193. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/registration.py +0 -0
  194. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/registry.py +0 -0
  195. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/types.py +0 -0
  196. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/__init__.py +0 -0
  197. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/client.py +0 -0
  198. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/config.py +0 -0
  199. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/definition.py +0 -0
  200. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/exceptions.py +0 -0
  201. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/tool.py +0 -0
  202. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/observability/__init__.py +0 -0
  203. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/observability/laminar.py +0 -0
  204. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/observability/utils.py +0 -0
  205. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/__init__.py +0 -0
  206. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/discovery.py +0 -0
  207. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/fetch.py +0 -0
  208. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/__init__.py +0 -0
  209. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  210. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
  211. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/base.py +0 -0
  212. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/claude_code.py +0 -0
  213. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
  214. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  215. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/installed.py +0 -0
  216. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/loader.py +0 -0
  217. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/plugin.py +0 -0
  218. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/source.py +0 -0
  219. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/types.py +0 -0
  220. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/__init__.py +0 -0
  221. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/agent_profile.py +0 -0
  222. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  223. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/profile_refs.py +0 -0
  224. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/resolver.py +0 -0
  225. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/seed.py +0 -0
  226. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/py.typed +0 -0
  227. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/secret/__init__.py +0 -0
  228. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/secret/secrets.py +0 -0
  229. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/__init__.py +0 -0
  230. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/_shell_ast.py +0 -0
  231. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/analyzer.py +0 -0
  232. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/confirmation_policy.py +0 -0
  233. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  234. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  235. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  236. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  237. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  238. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/ensemble.py +0 -0
  239. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/__init__.py +0 -0
  240. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  241. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/utils.py +0 -0
  242. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/llm_analyzer.py +0 -0
  243. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/risk.py +0 -0
  244. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/shell_parser.py +0 -0
  245. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/toolshield_helpers.py +0 -0
  246. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  247. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/__init__.py +0 -0
  248. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/acp_install_catalog.py +0 -0
  249. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/acp_providers.py +0 -0
  250. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/metadata.py +0 -0
  251. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/__init__.py +0 -0
  252. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/exceptions.py +0 -0
  253. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/execute.py +0 -0
  254. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/fetch.py +0 -0
  255. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/installed.py +0 -0
  256. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/skill.py +0 -0
  257. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/trigger.py +0 -0
  258. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/types.py +0 -0
  259. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/utils.py +0 -0
  260. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/__init__.py +0 -0
  261. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/load.py +0 -0
  262. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/registry.py +0 -0
  263. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/schema.py +0 -0
  264. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/testing/__init__.py +0 -0
  265. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/testing/test_llm.py +0 -0
  266. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/__init__.py +0 -0
  267. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/finish.py +0 -0
  268. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  269. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  270. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/think.py +0 -0
  271. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  272. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/client_tool.py +0 -0
  273. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/defaults.py +0 -0
  274. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/registry.py +0 -0
  275. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/schema.py +0 -0
  276. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/spec.py +0 -0
  277. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/tool.py +0 -0
  278. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/__init__.py +0 -0
  279. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/async_executor.py +0 -0
  280. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/async_utils.py +0 -0
  281. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/cipher.py +0 -0
  282. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/command.py +0 -0
  283. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/datetime.py +0 -0
  284. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/deprecation.py +0 -0
  285. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/files.py +0 -0
  286. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/github.py +0 -0
  287. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/json.py +0 -0
  288. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/masking.py +0 -0
  289. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/models.py +0 -0
  290. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/paging.py +0 -0
  291. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/path.py +0 -0
  292. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  293. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/redact.py +0 -0
  294. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/truncate.py +0 -0
  295. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/visualize.py +0 -0
  296. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/__init__.py +0 -0
  297. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/base.py +0 -0
  298. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/local.py +0 -0
  299. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/models.py +0 -0
  300. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/__init__.py +0 -0
  301. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  302. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/base.py +0 -0
  303. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  304. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/repo.py +0 -0
  305. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/workspace.py +0 -0
  306. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  307. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/requires.txt +0 -0
  308. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/top_level.txt +0 -0
  309. {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.49.5
3
+ Version: 1.49.6
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
@@ -55,6 +55,7 @@ from openhands.sdk.hooks import HookConfig, HookEventProcessor, create_hook_call
55
55
  from openhands.sdk.io import FileStore, LocalFileStore
56
56
  from openhands.sdk.llm import LLM, Message, TextContent, content_to_str
57
57
  from openhands.sdk.llm.auth.openai import create_subscription_llm_from_config
58
+ from openhands.sdk.llm.exceptions import LLMAuthenticationError
58
59
  from openhands.sdk.llm.llm import LLMCallContext
59
60
  from openhands.sdk.llm.llm_profile_store import LLMProfileStore
60
61
  from openhands.sdk.llm.llm_registry import LLMRegistry
@@ -2040,6 +2041,21 @@ class LocalConversation(BaseConversation):
2040
2041
  )
2041
2042
  )
2042
2043
  break
2044
+ except LLMAuthenticationError as e:
2045
+ with self._state:
2046
+ self._state.execution_status = ConversationExecutionStatus.ERROR
2047
+ self._on_event(
2048
+ ConversationErrorEvent(
2049
+ source="environment",
2050
+ code="LLMAuthenticationError",
2051
+ detail=(
2052
+ "Your LLM API key appears to be invalid or has expired."
2053
+ ),
2054
+ )
2055
+ )
2056
+ raise ConversationRunError(
2057
+ self._state.id, e, persistence_dir=self._state.persistence_dir
2058
+ ) from e
2043
2059
  except Exception as e:
2044
2060
  with self._state:
2045
2061
  self._state.execution_status = ConversationExecutionStatus.ERROR
@@ -2540,6 +2556,21 @@ class LocalConversation(BaseConversation):
2540
2556
 
2541
2557
  self._state.execution_status = ConversationExecutionStatus.PAUSED
2542
2558
  self._on_event(InterruptEvent())
2559
+ except LLMAuthenticationError as e:
2560
+ with self._state:
2561
+ self._state.execution_status = ConversationExecutionStatus.ERROR
2562
+ self._on_event(
2563
+ ConversationErrorEvent(
2564
+ source="environment",
2565
+ code="LLMAuthenticationError",
2566
+ detail=(
2567
+ "Your LLM API key appears to be invalid or has expired."
2568
+ ),
2569
+ )
2570
+ )
2571
+ raise ConversationRunError(
2572
+ self._state.id, e, persistence_dir=self._state.persistence_dir
2573
+ ) from e
2543
2574
  except Exception as e:
2544
2575
  with self._state:
2545
2576
  updated_agent_state = dict(self._state.agent_state)
@@ -566,9 +566,10 @@ class HookExecutor:
566
566
  try:
567
567
  output_data = json.loads(result.stdout)
568
568
  if isinstance(output_data, dict):
569
- # Parse decision
570
- if "decision" in output_data:
571
- decision_str = output_data["decision"].lower()
569
+ # Non-string values represent no decision.
570
+ decision_value = output_data.get("decision")
571
+ if isinstance(decision_value, str):
572
+ decision_str = decision_value.lower()
572
573
  if decision_str == "allow":
573
574
  hook_result.decision = HookDecision.ALLOW
574
575
  elif decision_str == "deny":
@@ -28,6 +28,12 @@ from openhands.sdk.llm.message import (
28
28
  ThinkingBlock,
29
29
  content_to_str,
30
30
  )
31
+ from openhands.sdk.llm.meta_profile_store import (
32
+ MetaProfile,
33
+ MetaProfileClass,
34
+ MetaProfileLimitExceeded,
35
+ MetaProfileStore,
36
+ )
31
37
  from openhands.sdk.llm.router import RouterLLM
32
38
  from openhands.sdk.llm.streaming import (
33
39
  AsyncTokenCallbackType,
@@ -61,6 +67,10 @@ __all__ = [
61
67
  "LLMProfileLoader",
62
68
  "LLMProfileMutator",
63
69
  "LLMProfileStore",
70
+ "MetaProfile",
71
+ "MetaProfileClass",
72
+ "MetaProfileLimitExceeded",
73
+ "MetaProfileStore",
64
74
  "RouterLLM",
65
75
  "RegistryEvent",
66
76
  # Messages
@@ -96,6 +96,7 @@ from openhands.sdk.llm.exceptions import (
96
96
  LLMNoResponseError,
97
97
  is_prompt_cache_too_small,
98
98
  is_quota_exhaustion_error,
99
+ looks_like_auth_error,
99
100
  map_provider_exception,
100
101
  )
101
102
 
@@ -661,6 +662,19 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
661
662
  # a result, so the read/check and store are kept atomic. ClassVar (shared);
662
663
  # critical sections are tiny and never cover network I/O.
663
664
  _runtime_metadata_lock: ClassVar[threading.Lock] = threading.Lock()
665
+ # Optional callback that returns a freshly-resolved API key. When set, an
666
+ # authentication failure (HTTP 401, e.g. a rotated/healed managed proxy key
667
+ # that LiteLLM reports as ``token_not_found_in_db``) triggers a single
668
+ # re-resolve of the key followed by one retry of the call. This is a
669
+ # near-term mitigation for stale credentials reaching a sandbox runtime
670
+ # agent; the durable fix is reference-only credential delivery (#4288).
671
+ # Held as a PrivateAttr so it is never serialized into conversation state.
672
+ _api_key_refresh_hook: Callable[[], str | SecretStr | None] | None = PrivateAttr(
673
+ default=None
674
+ )
675
+ # Recursion guard: set on the refreshed copy so a still-failing key does not
676
+ # loop. One refresh + one retry per call chain.
677
+ _auth_refresh_attempted: bool = PrivateAttr(default=False)
664
678
  model_config: ClassVar[ConfigDict] = ConfigDict(
665
679
  extra="ignore", arbitrary_types_allowed=True
666
680
  )
@@ -1011,6 +1025,72 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1011
1025
  raise mapped from error
1012
1026
  raise
1013
1027
 
1028
+ # =========================================================================
1029
+ # Authentication refresh (refresh-on-401)
1030
+ # =========================================================================
1031
+ def set_api_key_refresh_hook(
1032
+ self, hook: Callable[[], str | SecretStr | None] | None
1033
+ ) -> None:
1034
+ """Register a callback that re-resolves this LLM's API key on a 401.
1035
+
1036
+ When a completion/responses call fails with an authentication error,
1037
+ the hook is invoked once to obtain a fresh key; if it returns a new
1038
+ value the call is retried a single time with that key. This mitigates
1039
+ stale managed/proxy credentials reaching a sandbox runtime agent (e.g.
1040
+ after a server-side managed-key rotation or heal), where the first call
1041
+ would otherwise fail with ``401 token_not_found_in_db``.
1042
+
1043
+ The hook must be non-blocking / cheap; on the async paths it is invoked
1044
+ in a worker thread. It is stored as a private attribute and is never
1045
+ serialized into conversation state.
1046
+ """
1047
+ self._api_key_refresh_hook = hook
1048
+
1049
+ def _resolve_refreshed_api_key(self, error: Exception) -> SecretStr | None:
1050
+ """Return a fresh API key to retry with, or ``None`` to skip refresh.
1051
+
1052
+ Returns ``None`` unless every condition holds: the error looks like an
1053
+ authentication failure, a refresh hook is configured, no refresh has
1054
+ already been attempted in this call chain, this LLM uses ``api_key``
1055
+ auth, and the hook yields a key that differs from the current one.
1056
+ """
1057
+ if self._auth_refresh_attempted:
1058
+ return None
1059
+ if self._api_key_refresh_hook is None:
1060
+ return None
1061
+ if self.auth_type != "api_key":
1062
+ return None
1063
+ if not looks_like_auth_error(error):
1064
+ return None
1065
+ try:
1066
+ new_key = self._api_key_refresh_hook()
1067
+ except Exception:
1068
+ # A flaky hook must not mask the original authentication error;
1069
+ # log and skip the refresh so the caller sees the real 401.
1070
+ logger.warning(
1071
+ "API key refresh hook raised; skipping refresh and "
1072
+ "surfacing the original authentication error.",
1073
+ exc_info=True,
1074
+ )
1075
+ return None
1076
+ if new_key is None:
1077
+ return None
1078
+ new_secret = new_key if isinstance(new_key, SecretStr) else SecretStr(new_key)
1079
+ if not new_secret.get_secret_value():
1080
+ return None
1081
+ current = self._get_api_key_value()
1082
+ if current is not None and current == new_secret.get_secret_value():
1083
+ # A refresh that returns the same (still-rejected) key is useless;
1084
+ # skip the retry and surface the original error.
1085
+ return None
1086
+ return new_secret
1087
+
1088
+ def _auth_refreshed_llm(self, new_api_key: SecretStr) -> LLM:
1089
+ """Copy this LLM with a refreshed key and the recursion guard set."""
1090
+ refreshed = self.model_copy(update={"api_key": new_api_key})
1091
+ refreshed._auth_refresh_attempted = True
1092
+ return refreshed
1093
+
1014
1094
  # =========================================================================
1015
1095
  # Shared helpers for completion / acompletion / responses / aresponses
1016
1096
  # =========================================================================
@@ -1668,6 +1748,21 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1668
1748
  on_token=on_token,
1669
1749
  **_caller_kwargs,
1670
1750
  )
1751
+ # If the credential was rejected (401) and a refresh hook can supply
1752
+ # a fresh key, re-resolve it and retry the call exactly once.
1753
+ refreshed_key = self._resolve_refreshed_api_key(e)
1754
+ if refreshed_key is not None:
1755
+ logger.warning(
1756
+ "Authentication error; refreshing API key and retrying once."
1757
+ )
1758
+ return self._auth_refreshed_llm(refreshed_key).completion(
1759
+ messages,
1760
+ tools,
1761
+ add_security_risk_prediction=add_security_risk_prediction,
1762
+ on_token=on_token,
1763
+ call_context=call_context,
1764
+ **_caller_kwargs,
1765
+ )
1671
1766
  return self._handle_error(
1672
1767
  e,
1673
1768
  lambda fb: fb.completion(
@@ -1772,6 +1867,24 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1772
1867
  on_token=on_token,
1773
1868
  **_caller_kwargs,
1774
1869
  )
1870
+ # If the credential was rejected (401) and a refresh hook can supply
1871
+ # a fresh key, re-resolve it (off the event loop) and retry once.
1872
+ loop = asyncio.get_running_loop()
1873
+ refreshed_key = await loop.run_in_executor(
1874
+ None, self._resolve_refreshed_api_key, e
1875
+ )
1876
+ if refreshed_key is not None:
1877
+ logger.warning(
1878
+ "Authentication error; refreshing API key and retrying once."
1879
+ )
1880
+ return await self._auth_refreshed_llm(refreshed_key).acompletion(
1881
+ messages,
1882
+ tools,
1883
+ add_security_risk_prediction=add_security_risk_prediction,
1884
+ on_token=on_token,
1885
+ call_context=call_context,
1886
+ **_caller_kwargs,
1887
+ )
1775
1888
  # Fallback is synchronous; cast the token callback since the
1776
1889
  # fallback LLM's sync path accepts TokenCallbackType.
1777
1890
  _fb_token = cast("TokenCallbackType | None", on_token)
@@ -1938,6 +2051,23 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
1938
2051
  on_token=on_token,
1939
2052
  **_caller_kwargs,
1940
2053
  )
2054
+ # If the credential was rejected (401) and a refresh hook can supply
2055
+ # a fresh key, re-resolve it and retry the call exactly once.
2056
+ refreshed_key = self._resolve_refreshed_api_key(e)
2057
+ if refreshed_key is not None:
2058
+ logger.warning(
2059
+ "Authentication error; refreshing API key and retrying once."
2060
+ )
2061
+ return self._auth_refreshed_llm(refreshed_key).responses(
2062
+ messages,
2063
+ tools,
2064
+ include,
2065
+ store,
2066
+ add_security_risk_prediction=add_security_risk_prediction,
2067
+ on_token=on_token,
2068
+ call_context=call_context,
2069
+ **_caller_kwargs,
2070
+ )
1941
2071
  return self._handle_error(
1942
2072
  e,
1943
2073
  lambda fb: fb.responses(
@@ -2115,6 +2245,26 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
2115
2245
  on_token=on_token,
2116
2246
  **_caller_kwargs,
2117
2247
  )
2248
+ # If the credential was rejected (401) and a refresh hook can supply
2249
+ # a fresh key, re-resolve it (off the event loop) and retry once.
2250
+ loop = asyncio.get_running_loop()
2251
+ refreshed_key = await loop.run_in_executor(
2252
+ None, self._resolve_refreshed_api_key, e
2253
+ )
2254
+ if refreshed_key is not None:
2255
+ logger.warning(
2256
+ "Authentication error; refreshing API key and retrying once."
2257
+ )
2258
+ return await self._auth_refreshed_llm(refreshed_key).aresponses(
2259
+ messages,
2260
+ tools,
2261
+ include,
2262
+ store,
2263
+ add_security_risk_prediction=add_security_risk_prediction,
2264
+ on_token=on_token,
2265
+ call_context=call_context,
2266
+ **_caller_kwargs,
2267
+ )
2118
2268
  _fb_token = cast("TokenCallbackType | None", on_token)
2119
2269
  return await self._ahandle_error(
2120
2270
  e,
@@ -0,0 +1,274 @@
1
+ """JSON-file storage for meta-profiles.
2
+
3
+ Key invariant: every model reference in a meta-profile (``classifier_model``,
4
+ each class's ``model``, and direct-routing prompt outputs) is the *name of a
5
+ saved LLM profile* in
6
+ :class:`~openhands.sdk.llm.llm_profile_store.LLMProfileStore`, not a raw model
7
+ string — credentials/provider settings resolve through that store.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import re
15
+ import tempfile
16
+ from collections.abc import Iterator
17
+ from contextlib import contextmanager
18
+ from pathlib import Path
19
+ from typing import Any, Final
20
+
21
+ from filelock import FileLock, Timeout
22
+ from pydantic import BaseModel, Field, model_validator
23
+
24
+ from openhands.sdk.llm.llm_profile_store import PROFILE_NAME_REGEX
25
+ from openhands.sdk.logger import get_logger
26
+
27
+
28
+ _LOCK_TIMEOUT_SECONDS: Final[float] = 30.0
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ def default_meta_profile_dir() -> Path:
34
+ """Return the default directory for meta-profile storage.
35
+
36
+ Agent-server instances can set ``OH_PERSISTENCE_DIR`` to isolate all
37
+ persisted state. Honor that for bare ``MetaProfileStore()`` instances so
38
+ runtime tools read the same meta-profiles exposed by the HTTP API. Without
39
+ that env var, keep the standalone SDK default under ``~/.openhands``.
40
+ """
41
+ env_dir = os.environ.get("OH_PERSISTENCE_DIR")
42
+ if env_dir:
43
+ return Path(env_dir) / "meta-profiles"
44
+ return Path.home() / ".openhands" / "meta-profiles"
45
+
46
+
47
+ class MetaProfileLimitExceeded(Exception):
48
+ """Raised when saving would exceed the configured meta-profile limit."""
49
+
50
+
51
+ class MetaProfileClass(BaseModel):
52
+ """A single task category and the LLM profile that should handle it."""
53
+
54
+ description: str = Field(
55
+ description="Natural-language description of the kind of task this "
56
+ "class covers (e.g. 'task is UI oriented or requires looking at images')."
57
+ )
58
+ model: str = Field(
59
+ description="Name of the saved LLM profile to switch to for tasks "
60
+ "matching this class."
61
+ )
62
+
63
+
64
+ class MetaProfile(BaseModel):
65
+ """A declarative model-routing configuration."""
66
+
67
+ classifier_model: str = Field(
68
+ description="Name of the saved LLM profile used to classify the task."
69
+ )
70
+ classes: list[MetaProfileClass] = Field(
71
+ default_factory=list,
72
+ description="Ordered list of task classes and their target profiles.",
73
+ )
74
+ prompt_template: str | None = Field(
75
+ default=None,
76
+ description=(
77
+ "Optional direct-routing prompt template. When set, the classifier "
78
+ "receives this rendered prompt and should return JSON with a "
79
+ "`model` field instead of a class number."
80
+ ),
81
+ )
82
+ model_table: str | None = Field(
83
+ default=None,
84
+ description=(
85
+ "Optional text inserted into `{{ model_table }}` for direct-routing "
86
+ "prompts."
87
+ ),
88
+ )
89
+
90
+ @model_validator(mode="after")
91
+ def validate_routing_mode(self) -> MetaProfile:
92
+ """Require either structured classes or a direct prompt, not both."""
93
+ if self.prompt_template is None:
94
+ return self
95
+ if re.search(r"{{\s*instance_text\s*}}", self.prompt_template) is None:
96
+ raise ValueError(
97
+ "Direct-routing meta-profiles must include {{ instance_text }} "
98
+ "in prompt_template."
99
+ )
100
+ if self.classes:
101
+ raise ValueError("Direct-routing meta-profiles cannot also define classes.")
102
+ return self
103
+
104
+
105
+ class MetaProfileStore:
106
+ """Read meta-profiles from the default meta-profile directory."""
107
+
108
+ def __init__(self, base_dir: Path | str | None = None) -> None:
109
+ """Initialize the meta-profile store.
110
+
111
+ Args:
112
+ base_dir: Directory where meta-profiles are stored. Defaults to
113
+ ``$OH_PERSISTENCE_DIR/meta-profiles`` when set, otherwise
114
+ ``~/.openhands/meta-profiles``.
115
+ """
116
+ self.base_dir = (
117
+ Path(base_dir) if base_dir is not None else default_meta_profile_dir()
118
+ )
119
+ self.base_dir.mkdir(parents=True, exist_ok=True)
120
+ self._file_lock = FileLock(self.base_dir / ".meta-profiles.lock")
121
+
122
+ @contextmanager
123
+ def _acquire_lock(self, timeout: float = _LOCK_TIMEOUT_SECONDS) -> Iterator[None]:
124
+ """Acquire the file lock for safe concurrent access.
125
+
126
+ The lock is reentrant within a process, so methods that hold it may
127
+ call other locked methods (e.g. ``save`` calls ``list``).
128
+
129
+ Raises:
130
+ TimeoutError: If the lock cannot be acquired within ``timeout``.
131
+ """
132
+ try:
133
+ with self._file_lock.acquire(timeout=timeout):
134
+ yield
135
+ except Timeout:
136
+ logger.error(
137
+ f"[Meta-profile Store] Failed to acquire lock within {timeout}s"
138
+ )
139
+ raise TimeoutError(
140
+ f"Meta-profile store lock acquisition timed out after {timeout}s"
141
+ )
142
+
143
+ def list(self) -> list[str]:
144
+ """Return stored meta-profile names (without ``.json``), sorted.
145
+
146
+ The order is alphabetical, not chronological. Callers that fall back to
147
+ "the first available" meta-profile (when none is explicitly active) get
148
+ the alphabetically-first name, which is stable but not "most recent".
149
+ """
150
+ return sorted(
151
+ p.stem
152
+ for p in self.base_dir.glob("*.json")
153
+ if PROFILE_NAME_REGEX.match(p.stem)
154
+ )
155
+
156
+ def _get_path(self, name: str) -> Path:
157
+ clean_name = name.removesuffix(".json")
158
+ if not PROFILE_NAME_REGEX.match(clean_name):
159
+ raise ValueError(
160
+ f"Invalid meta-profile name: {name!r}. "
161
+ "Names must be 1-64 characters, start with a letter or digit, "
162
+ "and contain only letters, digits, '.', '_', or '-'."
163
+ )
164
+ return self.base_dir / f"{clean_name}.json"
165
+
166
+ def load(self, name: str) -> MetaProfile:
167
+ """Load a meta-profile by name.
168
+
169
+ Raises:
170
+ FileNotFoundError: If the meta-profile does not exist.
171
+ ValueError: If the file is corrupted or fails validation.
172
+ """
173
+ path = self._get_path(name)
174
+ if not path.exists():
175
+ existing = self.list()
176
+ raise FileNotFoundError(
177
+ f"Meta-profile `{name}` not found. "
178
+ f"Available meta-profiles: {', '.join(existing) or 'none'}"
179
+ )
180
+ try:
181
+ data = json.loads(path.read_text(encoding="utf-8"))
182
+ return MetaProfile.model_validate(data)
183
+ except Exception as e:
184
+ raise ValueError(f"Failed to load meta-profile `{name}`: {e}") from e
185
+
186
+ def save(
187
+ self,
188
+ name: str,
189
+ meta_profile: MetaProfile,
190
+ *,
191
+ max_profiles: int | None = None,
192
+ ) -> None:
193
+ """Persist a meta-profile under ``name`` (atomic write, overwrites).
194
+
195
+ Args:
196
+ name: Name of the meta-profile to save.
197
+ meta_profile: The meta-profile to persist.
198
+ max_profiles: Optional cap on the number of meta-profiles. When set,
199
+ raises :class:`MetaProfileLimitExceeded` if creating a *new*
200
+ meta-profile would exceed the limit.
201
+
202
+ Raises:
203
+ MetaProfileLimitExceeded: If ``max_profiles`` would be exceeded.
204
+ ValueError: If ``name`` is not a valid meta-profile name.
205
+ """
206
+ path = self._get_path(name)
207
+
208
+ # Hold the lock across the precondition check and the atomic replace so
209
+ # concurrent creators cannot both pass the limit check and overshoot
210
+ # ``max_profiles`` (TOCTOU), mirroring ``LLMProfileStore``.
211
+ with self._acquire_lock():
212
+ if max_profiles is not None and not path.exists():
213
+ if len(self.list()) >= max_profiles:
214
+ raise MetaProfileLimitExceeded(
215
+ f"Meta-profile limit reached ({max_profiles})."
216
+ )
217
+
218
+ profile_json = json.dumps(meta_profile.model_dump(mode="json"), indent=2)
219
+ with tempfile.NamedTemporaryFile(
220
+ mode="w",
221
+ dir=self.base_dir,
222
+ suffix=".tmp",
223
+ delete=False,
224
+ encoding="utf-8",
225
+ ) as tmp:
226
+ tmp.write(profile_json)
227
+ tmp_path = Path(tmp.name)
228
+
229
+ try:
230
+ Path.replace(tmp_path, path)
231
+ except Exception:
232
+ tmp_path.unlink(missing_ok=True)
233
+ raise
234
+ logger.info(f"Saved meta-profile `{name}` at {path}")
235
+
236
+ def delete(self, name: str) -> None:
237
+ """Delete a meta-profile (idempotent — missing names are a no-op).
238
+
239
+ Raises:
240
+ ValueError: If ``name`` is not a valid meta-profile name.
241
+ """
242
+ path = self._get_path(name)
243
+ with self._acquire_lock():
244
+ if not path.exists():
245
+ logger.info(f"Meta-profile `{name}` not found. Skipping delete.")
246
+ return
247
+ path.unlink()
248
+ logger.info(f"Deleted meta-profile `{name}`")
249
+
250
+ def list_summaries(self) -> list[dict[str, Any]]:
251
+ """List meta-profile metadata without full schema validation.
252
+
253
+ Files with corrupted JSON or non-dict top-level values are skipped with
254
+ a warning so a single bad file never breaks the listing.
255
+ """
256
+ summaries: list[dict[str, Any]] = []
257
+ for name in self.list():
258
+ try:
259
+ data = json.loads(self._get_path(name).read_text(encoding="utf-8"))
260
+ except (OSError, json.JSONDecodeError) as e:
261
+ logger.warning(f"Skipping corrupted meta-profile {name!r}: {e}")
262
+ continue
263
+ if not isinstance(data, dict):
264
+ logger.warning(f"Skipping non-dict meta-profile {name!r}")
265
+ continue
266
+ classes = data.get("classes") or []
267
+ summaries.append(
268
+ {
269
+ "name": name,
270
+ "classifier_model": data.get("classifier_model"),
271
+ "num_classes": len(classes) if isinstance(classes, list) else 0,
272
+ }
273
+ )
274
+ return summaries
@@ -109,7 +109,6 @@ def _normalized_supported_openai_params(model: str | None) -> frozenset[str]:
109
109
 
110
110
 
111
111
  REASONING_EFFORT_MODEL_OVERRIDES = {
112
- "gpt-5.2-codex": "gpt-5.2-codex",
113
112
  "kimi-k3": "moonshot/kimi-k3",
114
113
  }
115
114
 
@@ -11,6 +11,8 @@ stay. When a new version lands, drop the oldest one in the same line.
11
11
 
12
12
  # GPT: gpt-6 and gpt-5.6. Codex: gpt-5.3-codex and gpt-5.2-codex.
13
13
  VERIFIED_OPENAI_MODELS = [
14
+ "gpt-6-sol",
15
+ "gpt-6-luna",
14
16
  "gpt-6-astra",
15
17
  "gpt-5.6",
16
18
  "gpt-5.6-sol",
@@ -20,10 +22,10 @@ VERIFIED_OPENAI_MODELS = [
20
22
  "gpt-5.2-codex",
21
23
  ]
22
24
 
23
- # Opus: 5 and 4.8. Sonnet: 5 and 4.6. Haiku: 4.5. Fable: 5.1 and 5.
25
+ # Opus: 5.5 and 5. Sonnet: 5 and 4.6. Haiku: 4.5. Fable: 5.1 and 5.
24
26
  VERIFIED_ANTHROPIC_MODELS = [
27
+ "claude-opus-5-5",
25
28
  "claude-opus-5",
26
- "claude-opus-4-8",
27
29
  "claude-sonnet-5",
28
30
  "claude-sonnet-4-6",
29
31
  "claude-haiku-4-5-20251001",
@@ -104,12 +106,14 @@ VERIFIED_QWEN_MODELS = [
104
106
  # What the ``openhands/`` provider serves. Same rule; every entry must also be in
105
107
  # a provider list above, except OpenHands-only models.
106
108
  VERIFIED_OPENHANDS_MODELS = [
109
+ "claude-opus-5-5",
107
110
  "claude-opus-5",
108
- "claude-opus-4-8",
109
111
  "claude-sonnet-5",
110
112
  "claude-sonnet-4-6",
111
113
  "claude-fable-5-1",
112
114
  "claude-fable-5",
115
+ "gpt-6-sol",
116
+ "gpt-6-luna",
113
117
  "gpt-6-astra",
114
118
  "gpt-5.6",
115
119
  "gpt-5.3-codex",