power-loop 9.0.0__tar.gz → 10.0.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 (228) hide show
  1. {power_loop-9.0.0 → power_loop-10.0.0}/PKG-INFO +60 -1
  2. {power_loop-9.0.0 → power_loop-10.0.0}/README.md +59 -0
  3. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/__init__.py +31 -1
  4. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/anthropic_factory.py +13 -34
  5. power_loop-10.0.0/power_loop/_vendor/llm_client/exact_json.py +28 -0
  6. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/interface.py +5 -4
  7. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/llm_factory.py +46 -266
  8. power_loop-10.0.0/power_loop/_vendor/llm_client/usage_normalization.py +148 -0
  9. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/stateful_loop.py +1 -1
  10. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/event_payloads.py +3 -2
  11. power_loop-10.0.0/power_loop/core/control.py +14 -0
  12. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/pipeline.py +14 -4
  13. power_loop-10.0.0/power_loop/llm_metrics.py +283 -0
  14. power_loop-10.0.0/power_loop/runtime/invocation.py +58 -0
  15. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/jobs.py +4 -0
  16. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_attempt_query.py +1 -1
  17. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_attempts.py +16 -6
  18. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_calls.py +8 -1
  19. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/provider.py +3 -0
  20. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/response_capture.py +3 -0
  21. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/PKG-INFO +60 -1
  22. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/SOURCES.txt +4 -0
  23. power_loop-9.0.0/power_loop/runtime/invocation.py +0 -24
  24. {power_loop-9.0.0 → power_loop-10.0.0}/LICENSE +0 -0
  25. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/__init__.py +0 -0
  26. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
  27. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/attempt_scope.py +0 -0
  28. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
  29. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
  30. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
  31. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
  32. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/proxy_fallback.py +0 -0
  33. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/__init__.py +0 -0
  34. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/follow_up.py +0 -0
  35. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/sink.py +0 -0
  36. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/system_prompt.py +0 -0
  37. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/types.py +0 -0
  38. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/__init__.py +0 -0
  39. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/context_policy.py +0 -0
  40. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/errors.py +0 -0
  41. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/events.py +0 -0
  42. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/handlers.py +0 -0
  43. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/hook_contexts.py +0 -0
  44. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/hooks.py +0 -0
  45. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/messages.py +0 -0
  46. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/protocols.py +0 -0
  47. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/tools.py +0 -0
  48. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/__init__.py +0 -0
  49. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/_redact.py +0 -0
  50. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/jsonl_sink.py +0 -0
  51. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/logging_sink.py +0 -0
  52. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/mcp.py +0 -0
  53. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/metrics_sink.py +0 -0
  54. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/otel_sink.py +0 -0
  55. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/agent_context.py +0 -0
  56. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/events.py +0 -0
  57. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/hooks.py +0 -0
  58. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/pass_turn.py +0 -0
  59. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/phase.py +0 -0
  60. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/runner.py +0 -0
  61. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/state.py +0 -0
  62. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/py.typed +0 -0
  63. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/_tagged_processes.py +0 -0
  64. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/abort_outcomes.py +0 -0
  65. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/background.py +0 -0
  66. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/bash_capture.py +0 -0
  67. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/blackboard.py +0 -0
  68. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget.py +0 -0
  69. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget_contracts.py +0 -0
  70. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget_revisions.py +0 -0
  71. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget_scopes.py +0 -0
  72. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/cancellation.py +0 -0
  73. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/capability_probe.py +0 -0
  74. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/compact.py +0 -0
  75. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/completion.py +0 -0
  76. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/completion_windows.py +0 -0
  77. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/context_window_probe.py +0 -0
  78. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_handoff.py +0 -0
  79. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_recovery.py +0 -0
  80. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_retirement.py +0 -0
  81. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_task_exit.py +0 -0
  82. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/delegation.py +0 -0
  83. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/env.py +0 -0
  84. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/exec_backend.py +0 -0
  85. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/execution_membership.py +0 -0
  86. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/execution_roles.py +0 -0
  87. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/executor_lifecycle.py +0 -0
  88. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/executor_registry.py +0 -0
  89. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/finalization.py +0 -0
  90. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/fold.py +0 -0
  91. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/fold_adapter.py +0 -0
  92. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/fold_calls.py +0 -0
  93. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/history_projector.py +0 -0
  94. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/history_sanitize.py +0 -0
  95. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/host_send_steps.py +0 -0
  96. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/human_input.py +0 -0
  97. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/image_recall.py +0 -0
  98. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/input_dispositions.py +0 -0
  99. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/lineage.py +0 -0
  100. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_claim.py +0 -0
  101. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_completion.py +0 -0
  102. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_delivery.py +0 -0
  103. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_effects.py +0 -0
  104. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/memory.py +0 -0
  105. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/message_ref.py +0 -0
  106. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_budgets.py +0 -0
  107. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_exit.py +0 -0
  108. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/native_returns.py +0 -0
  109. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/nested_tools.py +0 -0
  110. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/node_inputs.py +0 -0
  111. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/node_resume.py +0 -0
  112. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/notes.py +0 -0
  113. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/nudges.py +0 -0
  114. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/original_owner_review.py +0 -0
  115. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/owner_waits.py +0 -0
  116. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pending_calls.py +0 -0
  117. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pending_native.py +0 -0
  118. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pending_owner.py +0 -0
  119. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pricing.py +0 -0
  120. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/process_control.py +0 -0
  121. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/processing.py +0 -0
  122. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/recall.py +0 -0
  123. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/representation.py +0 -0
  124. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/result_payloads.py +0 -0
  125. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/result_proof.py +0 -0
  126. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/result_revisions.py +0 -0
  127. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/retry.py +0 -0
  128. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/review_tools.py +0 -0
  129. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/root_context.py +0 -0
  130. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/root_control.py +0 -0
  131. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/runtime_state.py +0 -0
  132. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/send_receipt.py +0 -0
  133. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/session_store.py +0 -0
  134. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/skills.py +0 -0
  135. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/spec.py +0 -0
  136. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/steer.py +0 -0
  137. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/stop_policy.py +0 -0
  138. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/__init__.py +0 -0
  139. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/__init__.py +0 -0
  140. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/mysql.py +0 -0
  141. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/postgres.py +0 -0
  142. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
  143. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/capabilities.py +0 -0
  144. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/context_cache.py +0 -0
  145. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/db.py +0 -0
  146. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/dialect.py +0 -0
  147. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/factory.py +0 -0
  148. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/schema.py +0 -0
  149. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/store.py +0 -0
  150. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/task_schema.py +0 -0
  151. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/types.py +0 -0
  152. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/structured.py +0 -0
  153. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/stub_provider.py +0 -0
  154. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/subagent_output.py +0 -0
  155. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/subagent_resume.py +0 -0
  156. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_control.py +0 -0
  157. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_events.py +0 -0
  158. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_history.py +0 -0
  159. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_inspection.py +0 -0
  160. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_query.py +0 -0
  161. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_runtime.py +0 -0
  162. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_sessions.py +0 -0
  163. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/timer_backend.py +0 -0
  164. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/timer_waits.py +0 -0
  165. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/timers.py +0 -0
  166. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/todos.py +0 -0
  167. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_archive.py +0 -0
  168. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_attempts.py +0 -0
  169. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_billing.py +0 -0
  170. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_call_abort.py +0 -0
  171. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_call_plans.py +0 -0
  172. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_context.py +0 -0
  173. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_contracts.py +0 -0
  174. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_truncation.py +0 -0
  175. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/usage_estimate.py +0 -0
  176. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_decisions.py +0 -0
  177. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_dispositions.py +0 -0
  178. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_effects.py +0 -0
  179. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_notifications.py +0 -0
  180. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_reviews.py +0 -0
  181. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_terminal.py +0 -0
  182. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_controller.py +0 -0
  183. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_profile.py +0 -0
  184. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_resources.py +0 -0
  185. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore.py +0 -0
  186. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore_execution.py +0 -0
  187. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore_resources.py +0 -0
  188. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore_runner.py +0 -0
  189. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_results.py +0 -0
  190. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_resume.py +0 -0
  191. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_tasks.py +0 -0
  192. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/__init__.py +0 -0
  193. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/background_contract.py +0 -0
  194. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/blackboard.py +0 -0
  195. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/command_policy.py +0 -0
  196. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/command_rules.py +0 -0
  197. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/default_manifest.py +0 -0
  198. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/default_tools.py +0 -0
  199. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/jobs.py +0 -0
  200. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/registry.py +0 -0
  201. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/shell_contract.py +0 -0
  202. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/skill_contract.py +0 -0
  203. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/spawn_agent.py +0 -0
  204. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/wait_handoff.py +0 -0
  205. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/__init__.py +0 -0
  206. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/_process_guard.py +0 -0
  207. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/admission.py +0 -0
  208. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/api.py +0 -0
  209. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/continuation.py +0 -0
  210. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/control.py +0 -0
  211. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/engine.py +0 -0
  212. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/facade.py +0 -0
  213. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/introspect.py +0 -0
  214. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/journal.py +0 -0
  215. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/legacy_history.py +0 -0
  216. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/result.py +0 -0
  217. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/resume.py +0 -0
  218. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/runner.py +0 -0
  219. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/spec.py +0 -0
  220. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/subprocess_executor.py +0 -0
  221. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/tool.py +0 -0
  222. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/worker.py +0 -0
  223. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/yaml.py +0 -0
  224. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/dependency_links.txt +0 -0
  225. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/requires.txt +0 -0
  226. {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/top_level.txt +0 -0
  227. {power_loop-9.0.0 → power_loop-10.0.0}/pyproject.toml +0 -0
  228. {power_loop-9.0.0 → power_loop-10.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 9.0.0
3
+ Version: 10.0.0
4
4
  Summary: Embeddable agent execution kernel — LLM loop, hooks, events, tools, dynamic sub-agents.
5
5
  Author-email: zhangran <zhangran24@126.com>
6
6
  License: MIT
@@ -418,3 +418,62 @@ After the exact charge is locally durable, API delivery failure leaves a pending
418
418
  budget submission and preserves the real tool result. Reconciliation never calls
419
419
  the handler again or reads a replacement price. Managed recovery sources without
420
420
  an ordinary callback checkpoint still need a separate adapter.
421
+
422
+
423
+ ### Host lifecycle extensions
424
+
425
+ Hosts can raise `power_loop.core.control.ControlledLoopStop(reason, message=...)`
426
+ from session-entry or round-start hooks. The pipeline records a controlled stop,
427
+ keeps completed work, and skips memory summarization on that exit. Hosts own
428
+ quota policy and settlement; the SDK has no application currency or user model.
429
+
430
+ `power_loop.runtime.invocation.register_invocation_scope(name, factory, exporter=...)`
431
+ installs a synchronous context manager around each actual tool invocation. The
432
+ factory receives `session_id`, `assistant_seq`, `tool_call_id`, and `tool_name`.
433
+ An optional exporter returns a small JSON snapshot which is saved as
434
+ `host_contexts[name]` on original job creation. Replay retains the original saved
435
+ snapshot; hosts restore it when recovering a child job. Business snapshots do not
436
+ carry execution authority and do not replace the SDK's signed task request.
437
+ # Response usage and supplier cost (10.0)
438
+
439
+ `LLMResponse.token_usage` contains standardized token counts.
440
+ `LLMResponse.provider_cost` is an independent `ProviderCost(amount, currency)`;
441
+ `amount` is an exact decimal string and `None` means the supplier did not report
442
+ money. Explicit zero is a valid supplier invoice. `LLMTokenUsage.cost` is removed.
443
+
444
+ `TokenUsageExtractor` and `CostExtractor` have default implementations. OpenAI,
445
+ Anthropic and Gemini usage shapes are recognized through the same interfaces.
446
+ Override nested paths per connection with `LLMProviderConfig.response_extraction`:
447
+
448
+ ```python
449
+ config.response_extraction = {
450
+ "usage": {
451
+ "paths": {
452
+ "prompt_tokens": "data.usage.input",
453
+ "completion_tokens": "data.usage.output",
454
+ "prompt_cached_tokens": "data.usage.cache",
455
+ },
456
+ "on_error": "raise",
457
+ },
458
+ "cost": {
459
+ "amount_path": "data.billing.amount",
460
+ "currency_path": "data.billing.currency",
461
+ "currency": "USD", # explicit default when no currency field is present
462
+ "on_error": "raise",
463
+ },
464
+ }
465
+ ```
466
+
467
+ Blank mappings use defaults. Missing fields/container mismatches are normal
468
+ misses. Invalid values produce structured diagnostics. `on_error="continue"`
469
+ returns unknown supplier money, never an invented zero. The agent loop enforces
470
+ the default raise policy after retaining original response evidence and outside
471
+ the transport retry loop. A host can then price complete token usage, or retain
472
+ unknown financial responsibility when neither reliable cost nor usage is known.
473
+
474
+ `ResponseMetrics(config).inspect(raw_json)` validates offline with no model
475
+ request; `extract_usage` and `extract_cost` also accept custom implementations.
476
+ Trusted hosts may supply `usage.code` / `cost.code` snippets declaring
477
+ `extract(response)`. This is an internal extension, not a public code editor or
478
+ an untrusted-code execution service. Its standardized output and error policies
479
+ are identical to field mappings.
@@ -340,3 +340,62 @@ After the exact charge is locally durable, API delivery failure leaves a pending
340
340
  budget submission and preserves the real tool result. Reconciliation never calls
341
341
  the handler again or reads a replacement price. Managed recovery sources without
342
342
  an ordinary callback checkpoint still need a separate adapter.
343
+
344
+
345
+ ### Host lifecycle extensions
346
+
347
+ Hosts can raise `power_loop.core.control.ControlledLoopStop(reason, message=...)`
348
+ from session-entry or round-start hooks. The pipeline records a controlled stop,
349
+ keeps completed work, and skips memory summarization on that exit. Hosts own
350
+ quota policy and settlement; the SDK has no application currency or user model.
351
+
352
+ `power_loop.runtime.invocation.register_invocation_scope(name, factory, exporter=...)`
353
+ installs a synchronous context manager around each actual tool invocation. The
354
+ factory receives `session_id`, `assistant_seq`, `tool_call_id`, and `tool_name`.
355
+ An optional exporter returns a small JSON snapshot which is saved as
356
+ `host_contexts[name]` on original job creation. Replay retains the original saved
357
+ snapshot; hosts restore it when recovering a child job. Business snapshots do not
358
+ carry execution authority and do not replace the SDK's signed task request.
359
+ # Response usage and supplier cost (10.0)
360
+
361
+ `LLMResponse.token_usage` contains standardized token counts.
362
+ `LLMResponse.provider_cost` is an independent `ProviderCost(amount, currency)`;
363
+ `amount` is an exact decimal string and `None` means the supplier did not report
364
+ money. Explicit zero is a valid supplier invoice. `LLMTokenUsage.cost` is removed.
365
+
366
+ `TokenUsageExtractor` and `CostExtractor` have default implementations. OpenAI,
367
+ Anthropic and Gemini usage shapes are recognized through the same interfaces.
368
+ Override nested paths per connection with `LLMProviderConfig.response_extraction`:
369
+
370
+ ```python
371
+ config.response_extraction = {
372
+ "usage": {
373
+ "paths": {
374
+ "prompt_tokens": "data.usage.input",
375
+ "completion_tokens": "data.usage.output",
376
+ "prompt_cached_tokens": "data.usage.cache",
377
+ },
378
+ "on_error": "raise",
379
+ },
380
+ "cost": {
381
+ "amount_path": "data.billing.amount",
382
+ "currency_path": "data.billing.currency",
383
+ "currency": "USD", # explicit default when no currency field is present
384
+ "on_error": "raise",
385
+ },
386
+ }
387
+ ```
388
+
389
+ Blank mappings use defaults. Missing fields/container mismatches are normal
390
+ misses. Invalid values produce structured diagnostics. `on_error="continue"`
391
+ returns unknown supplier money, never an invented zero. The agent loop enforces
392
+ the default raise policy after retaining original response evidence and outside
393
+ the transport retry loop. A host can then price complete token usage, or retain
394
+ unknown financial responsibility when neither reliable cost nor usage is known.
395
+
396
+ `ResponseMetrics(config).inspect(raw_json)` validates offline with no model
397
+ request; `extract_usage` and `extract_cost` also accept custom implementations.
398
+ Trusted hosts may supply `usage.code` / `cost.code` snippets declaring
399
+ `extract(response)`. This is an internal extension, not a public code editor or
400
+ an untrusted-code execution service. Its standardized output and error policies
401
+ are identical to field mappings.
@@ -15,7 +15,7 @@ Stability tiers
15
15
  无版本承诺,可随时变更或删除。
16
16
  """
17
17
 
18
- __version__ = "9.0.0"
18
+ __version__ = "10.0.0"
19
19
 
20
20
  # Public LLM contract (SDK-free) re-exported so callers (e.g. writing llm.* hooks or
21
21
  # a custom LLMService) don't reach into the internal vendored transport package (H3.4).
@@ -265,6 +265,18 @@ from power_loop.tools.spawn_agent import (
265
265
  register_spawn_agent,
266
266
  )
267
267
 
268
+ from .llm_metrics import (
269
+ CostExtractor,
270
+ DefaultCostExtractor,
271
+ DefaultTokenUsageExtractor,
272
+ FieldCostExtractor,
273
+ FieldTokenUsageExtractor,
274
+ ProviderCost,
275
+ ResponseExtractionError,
276
+ ResponseMetrics,
277
+ TokenUsageExtractor,
278
+ )
279
+
268
280
  _logging.getLogger("power_loop").addHandler(_logging.NullHandler())
269
281
 
270
282
 
@@ -306,9 +318,27 @@ STABLE_API = (
306
318
  "ModelCapabilityError",
307
319
  "create_llm_service_from_env",
308
320
  "create_llm_service_from_config",
321
+ "ResponseMetrics",
322
+ "ProviderCost",
323
+ "TokenUsageExtractor",
324
+ "CostExtractor",
325
+ "DefaultTokenUsageExtractor",
326
+ "DefaultCostExtractor",
327
+ "FieldTokenUsageExtractor",
328
+ "FieldCostExtractor",
329
+ "ResponseExtractionError",
309
330
  )
310
331
 
311
332
  __all__ = [
333
+ "ResponseMetrics",
334
+ "ProviderCost",
335
+ "TokenUsageExtractor",
336
+ "CostExtractor",
337
+ "DefaultTokenUsageExtractor",
338
+ "DefaultCostExtractor",
339
+ "FieldTokenUsageExtractor",
340
+ "FieldCostExtractor",
341
+ "ResponseExtractionError",
312
342
  "PASS_TURN_TOOL",
313
343
  "request_pass",
314
344
  "pass_progress",
@@ -10,7 +10,8 @@ from typing import Any
10
10
  from anthropic import AsyncAnthropic, DefaultAsyncHttpxClient
11
11
 
12
12
  from .capabilities import ModelCapabilities, coerce_capabilities
13
- from .interface import AnthropicChatConfig, LLMRequest, LLMResponse, LLMService, LLMStreamChunk, LLMTokenUsage
13
+ from .exact_json import configure_http_client
14
+ from .interface import AnthropicChatConfig, LLMRequest, LLMResponse, LLMService, LLMStreamChunk
14
15
  from .llm_utils import parse_json_from_model_output_detailed
15
16
  from .multimodal import render_message_content
16
17
  from .proxy_fallback import env_proxy_http_client
@@ -77,7 +78,7 @@ class AnthropicMessagesLLMService(LLMService):
77
78
  base_url=self._cfg.base_url,
78
79
  timeout=self._cfg.timeout_s,
79
80
  max_retries=self._cfg.max_retries,
80
- http_client=env_proxy_http_client(DefaultAsyncHttpxClient),
81
+ http_client=configure_http_client(env_proxy_http_client(DefaultAsyncHttpxClient) or DefaultAsyncHttpxClient()),
81
82
  )
82
83
  return self._client.with_options(max_retries=0) if single_attempt.get() else self._client
83
84
 
@@ -89,39 +90,15 @@ class AnthropicMessagesLLMService(LLMService):
89
90
  finally:
90
91
  self._client = None
91
92
 
92
- def _usage_dict_from_any(self, usage: Any) -> dict[str, Any]:
93
- raw = _as_dict(usage)
93
+ def _usage_dict_from_any(self, usage: Any = None):
94
+ from power_loop.llm_metrics import DefaultTokenUsageExtractor
95
+ result = DefaultTokenUsageExtractor().extract({"usage": self._last_usage if usage is None else usage})
96
+ return result.as_dict() if result is not None else {}
94
97
 
95
- def _int_or_none(value: Any) -> int | None:
96
- try:
97
- return None if value is None else int(value)
98
- except Exception:
99
- return None
100
-
101
- prompt = _int_or_none(raw.get("input_tokens") or raw.get("prompt_tokens"))
102
- completion = _int_or_none(raw.get("output_tokens") or raw.get("completion_tokens"))
103
- total = _int_or_none(raw.get("total_tokens"))
104
- if total is None and prompt is not None and completion is not None:
105
- total = prompt + completion
106
- raw["prompt_tokens"] = prompt
107
- raw["completion_tokens"] = completion
108
- raw["total_tokens"] = total
109
- return raw
110
-
111
- def _usage_obj(self, usage: Any = None) -> LLMTokenUsage:
112
- d = self._last_usage if usage is None else self._usage_dict_from_any(usage)
113
-
114
- def _int_or_none(value: Any) -> int | None:
115
- try:
116
- return None if value is None else int(value)
117
- except Exception:
118
- return None
119
-
120
- return LLMTokenUsage(
121
- prompt_tokens=_int_or_none(d.get("prompt_tokens")),
122
- completion_tokens=_int_or_none(d.get("completion_tokens")),
123
- total_tokens=_int_or_none(d.get("total_tokens")),
124
- )
98
+ def _usage_obj(self, usage: Any = None):
99
+ from power_loop.llm_metrics import DefaultTokenUsageExtractor
100
+ result = DefaultTokenUsageExtractor().extract({"usage": self._last_usage if usage is None else usage})
101
+ return result
125
102
 
126
103
  def _record_usage(self, usage: Any) -> None:
127
104
  self._last_usage = self._usage_dict_from_any(usage)
@@ -407,6 +384,8 @@ class AnthropicMessagesLLMService(LLMService):
407
384
  maybe = on_stream_end(result)
408
385
  if hasattr(maybe, "__await__"):
409
386
  await maybe
387
+ from power_loop.llm_metrics import ResponseMetrics
388
+ ResponseMetrics(self._cfg.response_extraction).apply(result)
410
389
  return result
411
390
 
412
391
  async def stream(self, request: LLMRequest) -> AsyncIterator[LLMStreamChunk]:
@@ -0,0 +1,28 @@
1
+ """Retain JSON decimal literals before provider SDKs normalize their objects."""
2
+ import json
3
+ from decimal import Decimal
4
+
5
+
6
+ def exact_event(event):
7
+ event.json = lambda: json.loads(event.data, parse_float=Decimal)
8
+ return event
9
+
10
+
11
+ async def exact_response_json(response):
12
+ if getattr(response, '_power_loop_exact_json', False):
13
+ return
14
+ original = response.json
15
+
16
+ def parse(**options):
17
+ options.setdefault('parse_float', Decimal)
18
+ return original(**options)
19
+
20
+ response.json = parse
21
+ response._power_loop_exact_json = True
22
+
23
+
24
+ def configure_http_client(client):
25
+ hooks = client.event_hooks.setdefault('response', [])
26
+ if exact_response_json not in hooks:
27
+ hooks.append(exact_response_json)
28
+ return client
@@ -204,10 +204,6 @@ class LLMTokenUsage:
204
204
  accepted_tokens: int | None = None
205
205
  rejected_tokens: int | None = None
206
206
 
207
- #: Charge the provider itself reported for this call (e.g. OpenRouter's ``usage.cost``), in the
208
- #: provider's billing currency. ``None`` = not reported; hosts then price tokens themselves.
209
- cost: float | None = None
210
-
211
207
  def as_dict(self) -> dict[str, int | None]:
212
208
  return dict(self.__dict__)
213
209
 
@@ -242,6 +238,8 @@ class LLMStreamChunk:
242
238
  token_usage: LLMTokenUsage | None = None
243
239
  raw_event: Any = None
244
240
  is_final: bool = False
241
+ provider_cost: Any = None
242
+ response_metrics: dict[str, Any] = field(default_factory=dict)
245
243
 
246
244
 
247
245
  @dataclass
@@ -274,6 +272,7 @@ class LLMResponse:
274
272
  tool_calls: list[dict[str, Any]] = field(default_factory=list)
275
273
  # Raw streamed chunks captured when complete() is implemented via stream aggregation.
276
274
  stream_chunks: list[LLMStreamChunk] = field(default_factory=list)
275
+ provider_cost: Any = None
277
276
 
278
277
  def get_tool_calls(self) -> list[dict[str, Any]]:
279
278
  """
@@ -537,6 +536,7 @@ class OpenAICompatibleChatConfig:
537
536
  # (e.g. DashScope ``extra_body.enable_thinking``) on for a whole loop without touching the
538
537
  # pipeline's per-call LLMRequest.
539
538
  request_extra: dict[str, Any] = field(default_factory=dict)
539
+ response_extraction: dict[str, Any] = field(default_factory=dict)
540
540
 
541
541
  @property
542
542
  def is_ready(self) -> bool:
@@ -556,6 +556,7 @@ class AnthropicChatConfig:
556
556
  #: transport used to carry none at all, which is how ``attachment`` blocks reached it
557
557
  #: unrendered and got dropped as "unsupported content block".
558
558
  capabilities: dict[str, Any] = field(default_factory=dict)
559
+ response_extraction: dict[str, Any] = field(default_factory=dict)
559
560
 
560
561
  @property
561
562
  def is_ready(self) -> bool: