openhands-sdk 1.45.0__tar.gz → 1.46.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/PKG-INFO +1 -1
  2. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/fetch.py +8 -9
  3. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/model_info.py +79 -0
  4. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/telemetry.py +92 -14
  5. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/observability/laminar.py +28 -0
  6. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
  7. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/pyproject.toml +1 -1
  8. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/__init__.py +0 -0
  9. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/__init__.py +0 -0
  10. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/acp_agent.py +0 -0
  11. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  12. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/acp_models.py +0 -0
  13. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/acp_tracing.py +0 -0
  14. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/agent.py +0 -0
  15. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/base.py +0 -0
  16. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  17. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  18. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/response_dispatch.py +0 -0
  19. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/agent/utils.py +0 -0
  20. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/banner.py +0 -0
  21. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/__init__.py +0 -0
  22. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/agent_context.py +0 -0
  23. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  24. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/base.py +0 -0
  25. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  26. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  27. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  28. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
  29. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/condenser/utils.py +0 -0
  30. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/memory.py +0 -0
  31. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  32. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/presets.py +0 -0
  33. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  34. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/registry.py +0 -0
  35. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/section.py +0 -0
  36. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  37. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  38. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  39. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
  40. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  41. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  42. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/__init__.py +0 -0
  43. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  44. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  45. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/properties/base.py +0 -0
  46. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  47. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  48. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  49. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  50. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/context/view/view.py +0 -0
  51. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/__init__.py +0 -0
  52. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/base.py +0 -0
  53. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/cancellation.py +0 -0
  54. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/conversation.py +0 -0
  55. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  56. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/event_store.py +0 -0
  57. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  58. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/exceptions.py +0 -0
  59. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  60. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  61. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  62. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  63. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  64. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  65. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  66. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/impl/local_conversation.py +0 -0
  67. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
  68. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  69. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/request.py +0 -0
  70. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  71. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/response_utils.py +0 -0
  72. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/secret_registry.py +0 -0
  73. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/state.py +0 -0
  74. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  75. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/title_utils.py +0 -0
  76. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/types.py +0 -0
  77. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  78. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  79. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  80. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/credential.py +0 -0
  81. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/__init__.py +0 -0
  82. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/base.py +0 -0
  83. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  84. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  85. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  86. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  87. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  88. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  89. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  90. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  91. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  92. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/critic/result.py +0 -0
  93. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/__init__.py +0 -0
  94. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/acp_tool_call.py +0 -0
  95. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/base.py +0 -0
  96. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/condenser.py +0 -0
  97. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/conversation_error.py +0 -0
  98. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/conversation_state.py +0 -0
  99. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/error_classification.py +0 -0
  100. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/hook_execution.py +0 -0
  101. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_completion_log.py +0 -0
  102. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  103. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  104. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  105. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  106. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  107. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  108. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/resume_transcript.py +0 -0
  109. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/streaming_delta.py +0 -0
  110. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/token.py +0 -0
  111. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/types.py +0 -0
  112. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/event/user_action.py +0 -0
  113. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/__init__.py +0 -0
  114. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  115. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/installation/info.py +0 -0
  116. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  117. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/installation/manager.py +0 -0
  118. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  119. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  120. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/cached_repo.py +0 -0
  121. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/exceptions.py +0 -0
  122. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/git_changes.py +0 -0
  123. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/git_commits.py +0 -0
  124. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/git_diff.py +0 -0
  125. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/models.py +0 -0
  126. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/git/utils.py +0 -0
  127. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/hooks/__init__.py +0 -0
  128. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/hooks/config.py +0 -0
  129. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  130. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/hooks/executor.py +0 -0
  131. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/hooks/manager.py +0 -0
  132. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/hooks/types.py +0 -0
  133. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/io/__init__.py +0 -0
  134. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/io/base.py +0 -0
  135. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/io/cache.py +0 -0
  136. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/io/local.py +0 -0
  137. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/io/memory.py +0 -0
  138. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/__init__.py +0 -0
  139. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  140. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/auth/credentials.py +0 -0
  141. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/auth/openai.py +0 -0
  142. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
  143. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  144. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
  145. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  146. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  147. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  148. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/llm.py +0 -0
  149. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
  150. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/llm_registry.py +0 -0
  151. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/llm_response.py +0 -0
  152. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/message.py +0 -0
  153. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  154. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  155. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  156. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/options/__init__.py +0 -0
  157. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/options/chat_options.py +0 -0
  158. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/options/common.py +0 -0
  159. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/options/responses_options.py +0 -0
  160. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
  161. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/router/__init__.py +0 -0
  162. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/router/base.py +0 -0
  163. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  164. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  165. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/streaming.py +0 -0
  166. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  167. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  168. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  169. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  170. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/model_features.py +0 -0
  171. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  172. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  173. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  174. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  175. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  176. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
  177. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  178. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  179. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
  180. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  181. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/logger/__init__.py +0 -0
  182. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/logger/logger.py +0 -0
  183. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/logger/rolling.py +0 -0
  184. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/marketplace/__init__.py +0 -0
  185. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/marketplace/registration.py +0 -0
  186. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/marketplace/registry.py +0 -0
  187. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/marketplace/types.py +0 -0
  188. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/__init__.py +0 -0
  189. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/client.py +0 -0
  190. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/config.py +0 -0
  191. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/definition.py +0 -0
  192. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/exceptions.py +0 -0
  193. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/tool.py +0 -0
  194. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/mcp/utils.py +0 -0
  195. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/observability/__init__.py +0 -0
  196. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/observability/utils.py +0 -0
  197. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/__init__.py +0 -0
  198. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/discovery.py +0 -0
  199. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/fetch.py +0 -0
  200. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/format/__init__.py +0 -0
  201. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  202. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/format/base.py +0 -0
  203. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
  204. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  205. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/installed.py +0 -0
  206. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/loader.py +0 -0
  207. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/plugin.py +0 -0
  208. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/source.py +0 -0
  209. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/plugin/types.py +0 -0
  210. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/profiles/__init__.py +0 -0
  211. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/profiles/agent_profile.py +0 -0
  212. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  213. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/profiles/profile_refs.py +0 -0
  214. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/profiles/resolver.py +0 -0
  215. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/profiles/seed.py +0 -0
  216. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/py.typed +0 -0
  217. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/secret/__init__.py +0 -0
  218. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/secret/secrets.py +0 -0
  219. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/__init__.py +0 -0
  220. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/_shell_ast.py +0 -0
  221. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/analyzer.py +0 -0
  222. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  223. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  224. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  225. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  226. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  227. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  228. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/ensemble.py +0 -0
  229. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  230. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  231. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  232. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  233. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/risk.py +0 -0
  234. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/shell_parser.py +0 -0
  235. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
  236. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  237. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/settings/__init__.py +0 -0
  238. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
  239. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/settings/acp_providers.py +0 -0
  240. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/settings/api_models.py +0 -0
  241. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/settings/metadata.py +0 -0
  242. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/settings/model.py +0 -0
  243. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/__init__.py +0 -0
  244. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/exceptions.py +0 -0
  245. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/execute.py +0 -0
  246. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/fetch.py +0 -0
  247. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/installed.py +0 -0
  248. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/skill.py +0 -0
  249. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/trigger.py +0 -0
  250. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/types.py +0 -0
  251. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/skills/utils.py +0 -0
  252. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/subagent/__init__.py +0 -0
  253. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/subagent/load.py +0 -0
  254. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/subagent/registry.py +0 -0
  255. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/subagent/schema.py +0 -0
  256. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/testing/__init__.py +0 -0
  257. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/testing/test_llm.py +0 -0
  258. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/__init__.py +0 -0
  259. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
  260. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  261. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  262. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  263. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/builtins/think.py +0 -0
  264. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  265. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/client_tool.py +0 -0
  266. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/defaults.py +0 -0
  267. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/registry.py +0 -0
  268. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/schema.py +0 -0
  269. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/spec.py +0 -0
  270. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/tool/tool.py +0 -0
  271. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/__init__.py +0 -0
  272. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/async_executor.py +0 -0
  273. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/async_utils.py +0 -0
  274. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/cipher.py +0 -0
  275. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/command.py +0 -0
  276. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/datetime.py +0 -0
  277. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/deprecation.py +0 -0
  278. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/files.py +0 -0
  279. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/github.py +0 -0
  280. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/json.py +0 -0
  281. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/models.py +0 -0
  282. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/paging.py +0 -0
  283. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/path.py +0 -0
  284. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  285. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/redact.py +0 -0
  286. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/truncate.py +0 -0
  287. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/utils/visualize.py +0 -0
  288. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/__init__.py +0 -0
  289. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/base.py +0 -0
  290. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/local.py +0 -0
  291. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/models.py +0 -0
  292. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  293. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  294. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/remote/base.py +0 -0
  295. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  296. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/repo.py +0 -0
  297. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands/sdk/workspace/workspace.py +0 -0
  298. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands_sdk.egg-info/SOURCES.txt +0 -0
  299. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  300. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands_sdk.egg-info/requires.txt +0 -0
  301. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  302. {openhands_sdk-1.45.0 → openhands_sdk-1.46.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.45.0
3
+ Version: 1.46.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
@@ -109,7 +109,7 @@ def _resolve_local_source(url: str) -> Path:
109
109
 
110
110
 
111
111
  def _apply_subpath(base_path: Path, subpath: str | None, context: str) -> Path:
112
- """Apply a subpath to a base path, validating it exists.
112
+ """Apply a subpath to a base path, validating it exists and stays inside.
113
113
 
114
114
  Args:
115
115
  base_path: The root path.
@@ -120,12 +120,16 @@ def _apply_subpath(base_path: Path, subpath: str | None, context: str) -> Path:
120
120
  The final path (base_path if no subpath, otherwise base_path/subpath).
121
121
 
122
122
  Raises:
123
- ExtensionFetchError: If subpath doesn't exist.
123
+ ExtensionFetchError: If subpath escapes base_path or doesn't exist.
124
124
  """
125
125
  if not subpath:
126
126
  return base_path
127
127
 
128
128
  final_path = base_path / subpath.strip("/")
129
+ # Containment: a subpath must not climb out of the base via ".." or a symlink.
130
+ resolved_base = base_path.resolve()
131
+ if not final_path.resolve().is_relative_to(resolved_base):
132
+ raise ExtensionFetchError(f"Subdirectory '{subpath}' escapes {context}")
129
133
  if not final_path.exists():
130
134
  raise ExtensionFetchError(f"Subdirectory '{subpath}' not found in {context}")
131
135
  return final_path
@@ -191,13 +195,8 @@ def fetch_with_resolution(
191
195
  source_type, url = parse_extension_source(source)
192
196
 
193
197
  if source_type == SourceType.LOCAL:
194
- if repo_path is not None:
195
- raise ExtensionFetchError(
196
- f"repo_path is not supported for local extension sources. "
197
- f"Specify the full path directly instead of "
198
- f"source='{source}' + repo_path='{repo_path}'"
199
- )
200
- return _resolve_local_source(url), None
198
+ base_path = _resolve_local_source(url)
199
+ return _apply_subpath(base_path, repo_path, f"local source '{source}'"), None
201
200
 
202
201
  git = git_helper if git_helper is not None else GitHelper()
203
202
 
@@ -5,6 +5,7 @@ from logging import getLogger
5
5
  from typing import Any
6
6
 
7
7
  import httpx
8
+ import litellm
8
9
  from litellm import model_cost
9
10
  from litellm.utils import get_model_info
10
11
  from pydantic import SecretStr
@@ -24,6 +25,66 @@ def _merge_raw_model_metadata(model_info: Mapping[str, Any]) -> dict[str, Any]:
24
25
  return {**raw, **model_info}
25
26
 
26
27
 
28
+ # Pricing fields copied from the underlying model so a custom proxy alias
29
+ # becomes priceable in litellm's global `model_cost` map (#4816).
30
+ _PRICING_FIELDS: tuple[str, ...] = (
31
+ "input_cost_per_token",
32
+ "output_cost_per_token",
33
+ "cache_read_input_token_cost",
34
+ "cache_creation_input_token_cost",
35
+ "max_input_tokens",
36
+ "max_output_tokens",
37
+ "litellm_provider",
38
+ "mode",
39
+ )
40
+
41
+
42
+ def _register_proxy_alias_pricing(
43
+ alias: str,
44
+ underlying_model_info: Mapping[str, Any] | None,
45
+ proxy_model_info: Mapping[str, Any] | None,
46
+ ) -> None:
47
+ """Register a `litellm_proxy/*` alias into litellm's `model_cost` (#4816).
48
+
49
+ litellm strips the `litellm_proxy/` prefix and parses the first segment as
50
+ the provider, so a custom name like `prod/claude-sonnet-4-5-bedrock` makes
51
+ `cost_per_token` raise and the span is silently priced $0. Registering the
52
+ alias with the underlying model's pricing makes it priceable without
53
+ renaming the proxy model.
54
+ """
55
+ if not alias or alias in model_cost:
56
+ return
57
+ try:
58
+ litellm.cost_per_token(model=alias, prompt_tokens=1, completion_tokens=1)
59
+ return
60
+ except Exception:
61
+ pass
62
+
63
+ entry: dict[str, Any] = {}
64
+ for src in (underlying_model_info, proxy_model_info):
65
+ if isinstance(src, Mapping):
66
+ entry.update({f: src[f] for f in _PRICING_FIELDS if f in src})
67
+
68
+ # litellm's `get_llm_provider` cannot resolve `bedrock_converse` as a
69
+ # provider label for an unknown alias id (only ``bedrock`` is accepted), so
70
+ # normalize it before registering or `cost_per_token` keeps raising.
71
+
72
+ if entry.get("litellm_provider") == "bedrock_converse":
73
+ entry["litellm_provider"] = "bedrock"
74
+
75
+ if (
76
+ entry.get("input_cost_per_token") is None
77
+ and entry.get("output_cost_per_token") is None
78
+ ):
79
+ return
80
+
81
+ entry.setdefault("mode", "chat")
82
+ try:
83
+ litellm.register_model({alias: entry})
84
+ except Exception as e:
85
+ logger.debug("Failed to register litellm_proxy alias %r: %s", alias, e)
86
+
87
+
27
88
  @lru_cache
28
89
  def _get_model_info_from_litellm_proxy(
29
90
  secret_api_key: SecretStr | str | None,
@@ -61,6 +122,24 @@ def _get_model_info_from_litellm_proxy(
61
122
  if current:
62
123
  model_info = current.get("model_info")
63
124
  logger.debug(f"Got model info from litellm proxy: {model_info}")
125
+
126
+ # Make custom proxy aliases priceable so cost instrumentation does
127
+ # not silently record $0 (#4816).
128
+ underlying_model = current.get("litellm_params", {}).get("model")
129
+ underlying_model_info = None
130
+ if isinstance(underlying_model, str) and underlying_model != stripped:
131
+ try:
132
+ underlying_model_info = get_model_info(underlying_model)
133
+ except Exception as e:
134
+ logger.debug(
135
+ f"get_model_info(underlying={underlying_model}) failed: {e}"
136
+ )
137
+ _register_proxy_alias_pricing(
138
+ alias=stripped,
139
+ underlying_model_info=underlying_model_info,
140
+ proxy_model_info=model_info,
141
+ )
142
+
64
143
  return model_info
65
144
  except Exception as e:
66
145
  logger.debug(
@@ -50,6 +50,8 @@ class Telemetry(BaseModel):
50
50
  default=None
51
51
  )
52
52
  _stats_update_callback: Callable[[], None] | None = PrivateAttr(default=None)
53
+ _span_cm: Any = PrivateAttr(default=None)
54
+ _span: Any = PrivateAttr(default=None)
53
55
 
54
56
  model_config: ClassVar[ConfigDict] = ConfigDict(
55
57
  extra="forbid", arbitrary_types_allowed=True
@@ -79,6 +81,7 @@ class Telemetry(BaseModel):
79
81
  def on_request(self, telemetry_ctx: dict | None) -> None:
80
82
  self._req_start = time.time()
81
83
  self._req_ctx = telemetry_ctx or {}
84
+ self._open_span()
82
85
 
83
86
  def on_response(
84
87
  self,
@@ -115,7 +118,11 @@ class Telemetry(BaseModel):
115
118
  if self.log_enabled:
116
119
  self.log_llm_call(resp, cost, raw_resp=raw_resp)
117
120
 
118
- # 5) notify about stats update
121
+ # 5) authoritative cost + cache buckets onto the span, before it closes
122
+ self._annotate_span(cost, usage)
123
+ self._close_span()
124
+
125
+ # 6) notify about stats update
119
126
  if self._stats_update_callback is not None:
120
127
  try:
121
128
  self._stats_update_callback()
@@ -128,6 +135,7 @@ class Telemetry(BaseModel):
128
135
  # Best-effort logging for failed requests (so we can debug malformed
129
136
  # request payloads, e.g. orphaned Responses reasoning items).
130
137
  self._last_latency = time.time() - (self._req_start or time.time())
138
+ self._close_span(_err)
131
139
 
132
140
  if not self.log_enabled:
133
141
  return
@@ -217,16 +225,7 @@ class Telemetry(BaseModel):
217
225
  or 0
218
226
  )
219
227
 
220
- cache_read = 0
221
- p_details = getattr(usage, "prompt_tokens_details", None) or getattr(
222
- usage, "input_tokens_details", None
223
- )
224
- if p_details is not None:
225
- cache_read = int(getattr(p_details, "cached_tokens", 0) or 0)
226
-
227
- # Kimi-K2-thinking populate usage.cached_tokens field
228
- if not cache_read and hasattr(usage, "cached_tokens"):
229
- cache_read = int(getattr(usage, "cached_tokens", 0) or 0)
228
+ cache_read, cache_write = self._cache_buckets(usage)
230
229
 
231
230
  reasoning_tokens = 0
232
231
  c_details = getattr(usage, "completion_tokens_details", None) or getattr(
@@ -235,9 +234,6 @@ class Telemetry(BaseModel):
235
234
  if c_details is not None:
236
235
  reasoning_tokens = int(getattr(c_details, "reasoning_tokens", 0) or 0)
237
236
 
238
- # Chat-specific: litellm may set a hidden cache write field
239
- cache_write = int(getattr(usage, "_cache_creation_input_tokens", 0) or 0)
240
-
241
237
  self.metrics.add_token_usage(
242
238
  prompt_tokens=prompt_tokens,
243
239
  completion_tokens=completion_tokens,
@@ -248,6 +244,88 @@ class Telemetry(BaseModel):
248
244
  response_id=response_id,
249
245
  )
250
246
 
247
+ @staticmethod
248
+ def _cache_buckets(usage: Usage | ResponseAPIUsage) -> tuple[int, int]:
249
+ """Return ``(cache_read, cache_write)`` for either usage shape.
250
+
251
+ Single source of truth for both ``metrics`` and the span, so the trace
252
+ and the app's cost cannot disagree about the buckets.
253
+ """
254
+ cache_read = 0
255
+ p_details = getattr(usage, "prompt_tokens_details", None) or getattr(
256
+ usage, "input_tokens_details", None
257
+ )
258
+ if p_details is not None:
259
+ cache_read = int(getattr(p_details, "cached_tokens", 0) or 0)
260
+ # Kimi-K2-thinking populates usage.cached_tokens instead.
261
+ if not cache_read:
262
+ cache_read = int(getattr(usage, "cached_tokens", 0) or 0)
263
+ if not cache_read:
264
+ cache_read = int(getattr(usage, "cache_read_input_tokens", 0) or 0)
265
+
266
+ # litellm mirrors this onto a private attr; the public one and the
267
+ # details dict are both populated on some provider shapes only.
268
+ cache_write = int(getattr(usage, "_cache_creation_input_tokens", 0) or 0)
269
+ if not cache_write:
270
+ cache_write = int(getattr(usage, "cache_creation_input_tokens", 0) or 0)
271
+ if not cache_write and p_details is not None:
272
+ cache_write = int(getattr(p_details, "cache_creation_tokens", 0) or 0)
273
+ return cache_read, cache_write
274
+
275
+ # ---------- Observability span ----------
276
+ # These bracket one LLM call: ``on_request`` -> transport -> ``on_response``
277
+ # all run inside a single retry attempt, so the span is current while
278
+ # litellm executes and still open when the cost is known.
279
+ def _open_span(self) -> None:
280
+ self._close_span() # a retry re-enters on_request; never leak the old one
281
+ try:
282
+ # Imported lazily: openhands.sdk.observability pulls in
283
+ # openhands.sdk.event, which imports this module.
284
+ from openhands.sdk.observability.laminar import llm_call_span
285
+
286
+ cm = llm_call_span(f"llm.{self.model_name}")
287
+ self._span = cm.__enter__()
288
+ self._span_cm = cm
289
+ except Exception:
290
+ logger.debug("Failed to open LLM span", exc_info=True)
291
+ self._span = self._span_cm = None
292
+
293
+ def _annotate_span(
294
+ self, cost: float | None, usage: Usage | ResponseAPIUsage | None
295
+ ) -> None:
296
+ span = self._span
297
+ if span is None:
298
+ return
299
+ try:
300
+ if cost:
301
+ # Authoritative: _compute_cost prefers the proxy's
302
+ # x-litellm-response-cost header, which is already cache-aware
303
+ # and priced by the real backend rather than by a name lookup.
304
+ span.set_attribute("gen_ai.usage.cost", float(cost))
305
+ if usage is not None:
306
+ cache_read, cache_write = self._cache_buckets(usage)
307
+ # Emitted unconditionally: lmnr only reports these when
308
+ # prompt_tokens_details is populated, which not every provider
309
+ # shape does.
310
+ span.set_attribute("gen_ai.usage.cache_read_input_tokens", cache_read)
311
+ span.set_attribute(
312
+ "gen_ai.usage.cache_creation_input_tokens", cache_write
313
+ )
314
+ except Exception:
315
+ logger.debug("Failed to annotate LLM span", exc_info=True)
316
+
317
+ def _close_span(self, err: BaseException | None = None) -> None:
318
+ cm, self._span_cm, self._span = self._span_cm, None, None
319
+ if cm is None:
320
+ return
321
+ try:
322
+ if err is not None:
323
+ cm.__exit__(type(err), err, err.__traceback__)
324
+ else:
325
+ cm.__exit__(None, None, None)
326
+ except Exception:
327
+ logger.debug("Failed to close LLM span", exc_info=True)
328
+
251
329
  def _compute_cost(
252
330
  self,
253
331
  resp: ModelResponse | ResponsesAPIResponse,
@@ -404,6 +404,34 @@ def start_child_span(
404
404
  logger.debug("Failed to create observability child span", exc_info=True)
405
405
 
406
406
 
407
+ @contextlib.contextmanager
408
+ def llm_call_span(name: str) -> Iterator[Any]:
409
+ """Open a span that stays current for the duration of one LLM call.
410
+
411
+ lmnr's LiteLLM instrumentation builds its span with ``Laminar.start_span``
412
+ (detached from the OTel context) and ends it in a ``finally`` before
413
+ ``litellm.completion`` returns, so attributes cannot be attached to it
414
+ afterwards -- OTel silently drops writes to an ended span. This span is
415
+ *current* while the call runs, so lmnr's span nests underneath it, and it
416
+ stays open long enough for Telemetry to attach the authoritative cost.
417
+
418
+ Yields ``None`` when observability is disabled, so callers stay branch-free.
419
+ """
420
+ if not should_enable_observability():
421
+ yield None
422
+ return
423
+ try:
424
+ from lmnr import Laminar
425
+
426
+ cm = Laminar.start_as_current_span(name=name, span_type="LLM")
427
+ except Exception:
428
+ logger.debug("Failed to open LLM observability span", exc_info=True)
429
+ yield None
430
+ return
431
+ with cm as span:
432
+ yield span
433
+
434
+
407
435
  # Trace-metadata key set by software-agent-sdk#4010 on the parent's `task`
408
436
  # TOOL span; copied onto a detached delegate trace when present so the
409
437
  # originating tool call is visible from the delegate's trace alone.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.45.0
3
+ Version: 1.46.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-sdk"
3
- version = "1.45.0"
3
+ version = "1.46.0"
4
4
  description = "OpenHands SDK - Core functionality for building AI agents"
5
5
 
6
6
  requires-python = ">=3.12"