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.
- {power_loop-9.0.0 → power_loop-10.0.0}/PKG-INFO +60 -1
- {power_loop-9.0.0 → power_loop-10.0.0}/README.md +59 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/__init__.py +31 -1
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/anthropic_factory.py +13 -34
- power_loop-10.0.0/power_loop/_vendor/llm_client/exact_json.py +28 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/interface.py +5 -4
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/llm_factory.py +46 -266
- power_loop-10.0.0/power_loop/_vendor/llm_client/usage_normalization.py +148 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/stateful_loop.py +1 -1
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/event_payloads.py +3 -2
- power_loop-10.0.0/power_loop/core/control.py +14 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/pipeline.py +14 -4
- power_loop-10.0.0/power_loop/llm_metrics.py +283 -0
- power_loop-10.0.0/power_loop/runtime/invocation.py +58 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/jobs.py +4 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_attempt_query.py +1 -1
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_attempts.py +16 -6
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_calls.py +8 -1
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/provider.py +3 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/response_capture.py +3 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/PKG-INFO +60 -1
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/SOURCES.txt +4 -0
- power_loop-9.0.0/power_loop/runtime/invocation.py +0 -24
- {power_loop-9.0.0 → power_loop-10.0.0}/LICENSE +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/attempt_scope.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/_vendor/llm_client/proxy_fallback.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/follow_up.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/sink.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/system_prompt.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/agent/types.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/context_policy.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/errors.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/events.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/handlers.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/hook_contexts.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/hooks.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/messages.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/protocols.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contracts/tools.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/_redact.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/jsonl_sink.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/logging_sink.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/mcp.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/metrics_sink.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/contrib/otel_sink.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/agent_context.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/events.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/hooks.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/pass_turn.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/phase.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/runner.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/core/state.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/py.typed +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/_tagged_processes.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/abort_outcomes.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/background.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/bash_capture.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/blackboard.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget_contracts.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget_revisions.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/budget_scopes.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/cancellation.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/capability_probe.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/compact.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/completion.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/completion_windows.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/context_window_probe.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_handoff.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_recovery.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_retirement.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/controller_task_exit.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/delegation.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/env.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/exec_backend.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/execution_membership.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/execution_roles.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/executor_lifecycle.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/executor_registry.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/finalization.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/fold.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/fold_adapter.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/fold_calls.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/history_projector.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/history_sanitize.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/host_send_steps.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/human_input.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/image_recall.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/input_dispositions.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/lineage.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_claim.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_completion.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_delivery.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/managed_effects.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/memory.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/message_ref.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_budgets.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/model_exit.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/native_returns.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/nested_tools.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/node_inputs.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/node_resume.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/notes.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/nudges.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/original_owner_review.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/owner_waits.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pending_calls.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pending_native.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pending_owner.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/pricing.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/process_control.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/processing.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/recall.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/representation.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/result_payloads.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/result_proof.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/result_revisions.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/retry.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/review_tools.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/root_context.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/root_control.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/runtime_state.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/send_receipt.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/session_store.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/skills.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/spec.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/steer.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/stop_policy.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/mysql.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/postgres.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/capabilities.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/context_cache.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/db.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/dialect.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/factory.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/schema.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/store.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/task_schema.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/store/types.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/structured.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/stub_provider.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/subagent_output.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/subagent_resume.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_control.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_events.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_history.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_inspection.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_query.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_runtime.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/task_sessions.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/timer_backend.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/timer_waits.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/timers.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/todos.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_archive.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_attempts.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_billing.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_call_abort.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_call_plans.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_context.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_contracts.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/tool_truncation.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/usage_estimate.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_decisions.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_dispositions.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_effects.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_notifications.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_reviews.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/wait_terminal.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_controller.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_profile.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_resources.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore_execution.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore_resources.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_restore_runner.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_results.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_resume.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/runtime/workflow_tasks.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/background_contract.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/blackboard.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/command_policy.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/command_rules.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/default_manifest.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/default_tools.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/jobs.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/registry.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/shell_contract.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/skill_contract.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/spawn_agent.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/tools/wait_handoff.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/__init__.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/_process_guard.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/admission.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/api.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/continuation.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/control.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/engine.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/facade.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/introspect.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/journal.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/legacy_history.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/result.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/resume.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/runner.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/spec.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/subprocess_executor.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/tool.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/worker.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop/workflow/yaml.py +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/dependency_links.txt +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/requires.txt +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/power_loop.egg-info/top_level.txt +0 -0
- {power_loop-9.0.0 → power_loop-10.0.0}/pyproject.toml +0 -0
- {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:
|
|
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__ = "
|
|
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 .
|
|
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
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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:
|