capability-runtime 0.1.1__tar.gz → 0.1.2__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 (139) hide show
  1. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/PKG-INFO +1 -1
  2. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/pyproject.toml +1 -1
  3. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/__init__.py +1 -1
  4. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/adapters/agently_backend.py +19 -3
  5. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/reporting/node_report.py +20 -1
  6. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/types.py +3 -0
  7. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/utils/usage.py +26 -6
  8. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/PKG-INFO +1 -1
  9. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_agently_backend.py +85 -1
  10. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_node_report_builder.py +53 -0
  11. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_node_report_contract_v1.py +12 -0
  12. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_release_tag_version_guardrail.py +2 -2
  13. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/README.md +0 -0
  14. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/setup.cfg +0 -0
  15. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/adapters/__init__.py +0 -0
  16. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/adapters/agent_adapter.py +0 -0
  17. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/adapters/triggerflow_workflow_engine.py +0 -0
  18. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/adapters/workflow_engine.py +0 -0
  19. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/config.py +0 -0
  20. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/errors.py +0 -0
  21. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/guards.py +0 -0
  22. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_protocol.py +0 -0
  23. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/__init__.py +0 -0
  24. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/approvals_profiles.py +0 -0
  25. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/evidence_hooks.py +0 -0
  26. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/history.py +0 -0
  27. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/invoke_capability.py +0 -0
  28. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/resume.py +0 -0
  29. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/system_prompt.py +0 -0
  30. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/turn_delta.py +0 -0
  31. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/logging_utils.py +0 -0
  32. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/manifest.py +0 -0
  33. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/output_validator.py +0 -0
  34. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/protocol/__init__.py +0 -0
  35. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/protocol/agent.py +0 -0
  36. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/protocol/capability.py +0 -0
  37. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/protocol/chat_backend.py +0 -0
  38. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/protocol/context.py +0 -0
  39. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/protocol/workflow.py +0 -0
  40. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/registry.py +0 -0
  41. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/reporting/__init__.py +0 -0
  42. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/runtime.py +0 -0
  43. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/runtime_ui_events_mixin.py +0 -0
  44. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/sdk_lifecycle.py +0 -0
  45. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/service_facade.py +0 -0
  46. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/services.py +0 -0
  47. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/structured_output.py +0 -0
  48. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/structured_stream.py +0 -0
  49. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/__init__.py +0 -0
  50. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/projector.py +0 -0
  51. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/session.py +0 -0
  52. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/store.py +0 -0
  53. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/transport.py +0 -0
  54. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/v1.py +0 -0
  55. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/upstream_compat.py +0 -0
  56. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/utils/__init__.py +0 -0
  57. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime/workflow_runtime.py +0 -0
  58. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/SOURCES.txt +0 -0
  59. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/dependency_links.txt +0 -0
  60. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/requires.txt +0 -0
  61. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/top_level.txt +0 -0
  62. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_agently_backend_replay.py +0 -0
  63. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_backend_mode.py +0 -0
  64. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_bilingual_docs_surface.py +0 -0
  65. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_bridge_artifacts_passthrough.py +0 -0
  66. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_bridge_register_tool_public_api.py +0 -0
  67. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_coding_agent_examples_atomic.py +0 -0
  68. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_coding_agent_examples_recipes.py +0 -0
  69. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_config_glue.py +0 -0
  70. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_dependency_pins.py +0 -0
  71. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_docs_pinned_dependency_versions.py +0 -0
  72. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_docs_scheme2_no_skilladapter_residue.py +0 -0
  73. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_error_observability.py +0 -0
  74. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_alignment_fixes_l1.py +0 -0
  75. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_no_agent_sdk_imports.py +0 -0
  76. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_real_evidence_strict_integration.py +0 -0
  77. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_real_integration.py +0 -0
  78. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_smoke.py +0 -0
  79. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_offline.py +0 -0
  80. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_real_fallback.py +0 -0
  81. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_real_integration.py +0 -0
  82. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_ui_contract.py +0 -0
  83. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_examples_workflow_skills_first_smoke.py +0 -0
  84. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_guards.py +0 -0
  85. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_approvals_profiles.py +0 -0
  86. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_history_assembler.py +0 -0
  87. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_invoke_capability.py +0 -0
  88. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_invoke_capability_shared_runtime.py +0 -0
  89. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_resume_helper.py +0 -0
  90. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_system_prompt_evidence.py +0 -0
  91. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_host_toolkit_turn_delta.py +0 -0
  92. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_integration_agently_requester_smoke.py +0 -0
  93. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_integration_approval_event_shape.py +0 -0
  94. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_integration_sources_redis_pgsql_smoke.py +0 -0
  95. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_loop.py +0 -0
  96. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_node_report_engine_identity_contract.py +0 -0
  97. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_offline_backend_injection_evidence.py +0 -0
  98. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_per_capability_llm_config_model_routing.py +0 -0
  99. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_preflight_gate.py +0 -0
  100. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_project_identity_naming_matrix.py +0 -0
  101. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_public_api_exports.py +0 -0
  102. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_public_repo_hygiene.py +0 -0
  103. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_qa_agent.py +0 -0
  104. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_registry.py +0 -0
  105. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_replay_tool_calls_alignment.py +0 -0
  106. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_repo_hygiene_no_tracked_env_or_pyc.py +0 -0
  107. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_repo_no_deep_imports_in_user_facing_docs.py +0 -0
  108. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_review_followups_module_contracts.py +0 -0
  109. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_bridge_fake_backend.py +0 -0
  110. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_concurrency.py +0 -0
  111. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_engine.py +0 -0
  112. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_hitl_host_protocol.py +0 -0
  113. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_hooks_and_schema_gate.py +0 -0
  114. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_initial_history_and_meta.py +0 -0
  115. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_manifest.py +0 -0
  116. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_run_stream_semantics_v1.py +0 -0
  117. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_service_facade_rpc_mapping.py +0 -0
  118. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_service_session_bridge.py +0 -0
  119. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_status_mapping.py +0 -0
  120. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_structured_output_bridge.py +0 -0
  121. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_runtime_structured_stream.py +0 -0
  122. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_sandbox_permissions_passthrough_contract.py +0 -0
  123. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_services_call_callback.py +0 -0
  124. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_services_map_node_status.py +0 -0
  125. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_skills_conformance_smoke.py +0 -0
  126. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_adapter_tool_registration.py +0 -0
  127. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_chat_backend_protocol_contract.py +0 -0
  128. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_chat_sse_usage_contract.py +0 -0
  129. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_compat_spaces_schema.py +0 -0
  130. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_prompt_profile_contract.py +0 -0
  131. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_runtime_client_server_contract.py +0 -0
  132. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_sandbox_profile_precedence_contract.py +0 -0
  133. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_skills_bundles_contract.py +0 -0
  134. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_tool_descriptor_compat.py +0 -0
  135. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_upstream_verification.py +0 -0
  136. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_versioning_strategy_guard.py +0 -0
  137. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_wal_backend_injection_contract.py +0 -0
  138. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_wal_locator_resolution_contract.py +0 -0
  139. {capability_runtime-0.1.1 → capability_runtime-0.1.2}/tests/test_workflow_host_runtime_surface.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capability-runtime
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events).
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capability-runtime"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events)."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,7 +1,7 @@
1
1
  """capability-runtime:统一 Runtime 入口(能力协议 + 执行 + 报告)。"""
2
2
  from __future__ import annotations
3
3
 
4
- __version__ = "0.1.1"
4
+ __version__ = "0.1.2"
5
5
 
6
6
  # === 统一入口 ===
7
7
  from .config import CustomTool, RuntimeConfig
@@ -66,13 +66,19 @@ class AgentlyBackendConfig:
66
66
 
67
67
  requester_factory: AgentlyRequesterFactory
68
68
 
69
- def _normalize_usage_payload(*, usage: Any, model: Any = None) -> Optional[Dict[str, Any]]:
69
+ def _normalize_usage_payload(
70
+ *,
71
+ usage: Any,
72
+ model: Any = None,
73
+ request_id: Any = None,
74
+ provider: Any = None,
75
+ ) -> Optional[Dict[str, Any]]:
70
76
  """
71
77
  把 provider usage 归一为 capability-runtime 的 `llm_usage` payload 形状。
72
78
 
73
79
  返回:
74
80
  - `None`:无法提取任何有效 usage 字段
75
- - `dict`:`model/input_tokens/output_tokens/total_tokens`
81
+ - `dict`:`model/input_tokens/output_tokens/total_tokens/request_id/provider`
76
82
  """
77
83
 
78
84
  if not isinstance(usage, dict):
@@ -93,6 +99,8 @@ def _normalize_usage_payload(*, usage: Any, model: Any = None) -> Optional[Dict[
93
99
  "input_tokens": input_tokens,
94
100
  "output_tokens": output_tokens,
95
101
  "total_tokens": total_tokens,
102
+ "request_id": request_id.strip() if isinstance(request_id, str) and request_id.strip() else None,
103
+ "provider": provider.strip() if isinstance(provider, str) and provider.strip() else None,
96
104
  }
97
105
  return payload if any(value is not None for value in payload.values()) else None
98
106
 
@@ -120,7 +128,15 @@ def _extract_usage_payload_from_sse_data(data: str) -> Optional[Dict[str, Any]]:
120
128
  return None
121
129
  if not isinstance(obj, dict):
122
130
  return None
123
- return _normalize_usage_payload(usage=obj.get("usage"), model=obj.get("model"))
131
+ request_id = obj.get("request_id")
132
+ if not (isinstance(request_id, str) and request_id.strip()):
133
+ request_id = obj.get("id")
134
+ return _normalize_usage_payload(
135
+ usage=obj.get("usage"),
136
+ model=obj.get("model"),
137
+ request_id=request_id,
138
+ provider=obj.get("provider"),
139
+ )
124
140
 
125
141
 
126
142
  def _merge_stream_options_for_usage(value: Any) -> Dict[str, Any]:
@@ -161,6 +161,8 @@ class NodeReportBuilder:
161
161
  usage_input_seen = False
162
162
  usage_output_seen = False
163
163
  usage_total_seen = False
164
+ usage_request_id: Optional[str] = None
165
+ usage_provider: Optional[str] = None
164
166
 
165
167
  def _ensure_tool(call_id: str, *, name: str) -> Dict[str, Any]:
166
168
  """获取/初始化工具调用聚合槽位(以 call_id 为主键)。"""
@@ -217,6 +219,14 @@ class NodeReportBuilder:
217
219
  if isinstance(model_name, str) and model_name.strip():
218
220
  usage_model = model_name.strip()
219
221
 
222
+ request_id = usage_summary.get("request_id")
223
+ if isinstance(request_id, str) and request_id.strip():
224
+ usage_request_id = request_id.strip()
225
+
226
+ provider = usage_summary.get("provider")
227
+ if isinstance(provider, str) and provider.strip():
228
+ usage_provider = provider.strip()
229
+
220
230
  input_tokens = usage_summary.get("input_tokens")
221
231
  if isinstance(input_tokens, int):
222
232
  usage_input_total += input_tokens
@@ -378,12 +388,21 @@ class NodeReportBuilder:
378
388
  NodeToolCallReport.model_validate(item) for item in tool_calls.values() if isinstance(item, dict)
379
389
  ]
380
390
  usage_report: Optional[NodeUsageReport] = None
381
- if usage_model is not None or usage_input_seen or usage_output_seen or usage_total_seen:
391
+ if (
392
+ usage_model is not None
393
+ or usage_input_seen
394
+ or usage_output_seen
395
+ or usage_total_seen
396
+ or usage_request_id is not None
397
+ or usage_provider is not None
398
+ ):
382
399
  usage_report = NodeUsageReport(
383
400
  model=usage_model,
384
401
  input_tokens=usage_input_total if usage_input_seen else None,
385
402
  output_tokens=usage_output_total if usage_output_seen else None,
386
403
  total_tokens=usage_total_total if usage_total_seen else None,
404
+ request_id=usage_request_id,
405
+ provider=usage_provider,
387
406
  )
388
407
 
389
408
  report = NodeReport(
@@ -48,6 +48,7 @@ class NodeUsageReport(BaseModel):
48
48
 
49
49
  字段说明:
50
50
  - `model`:best-effort 记录本次运行最后一次可识别的模型名。
51
+ - `request_id` / `provider`:provider/gateway 请求元数据,用于宿主观测、对账与追踪。
51
52
  - token 字段缺失时保持 None,禁止伪造 0。
52
53
  """
53
54
 
@@ -57,6 +58,8 @@ class NodeUsageReport(BaseModel):
57
58
  input_tokens: Optional[int] = None
58
59
  output_tokens: Optional[int] = None
59
60
  total_tokens: Optional[int] = None
61
+ request_id: Optional[str] = None
62
+ provider: Optional[str] = None
60
63
 
61
64
 
62
65
  class NodeReport(BaseModel):
@@ -4,6 +4,7 @@ Usage 提取工具:从 LLM usage payload 中提取标准化指标。
4
4
  兼容:
5
5
  - 本仓规范字段:`input_tokens/output_tokens/total_tokens`
6
6
  - OpenAI 风格字段:`prompt_tokens/completion_tokens/total_tokens`
7
+ - provider metadata:`request_id/id/provider`
7
8
  - 可选嵌套:`payload["usage"]`
8
9
  """
9
10
 
@@ -26,6 +27,12 @@ def _usage_int(value: Any) -> Optional[int]:
26
27
  return parsed if parsed >= 0 else None
27
28
 
28
29
 
30
+ def _usage_text(value: Any) -> Optional[str]:
31
+ """把 provider metadata 字符串归一为空值安全的文本。"""
32
+
33
+ return value.strip() if isinstance(value, str) and value.strip() else None
34
+
35
+
29
36
  def extract_usage_metrics(payload: Any) -> Dict[str, Optional[Any]]:
30
37
  """
31
38
  从 `llm_usage` payload 中提取 usage 摘要。
@@ -34,18 +41,29 @@ def extract_usage_metrics(payload: Any) -> Dict[str, Optional[Any]]:
34
41
  - payload:AgentEvent.payload 或类似结构
35
42
 
36
43
  返回:
37
- - dict 包含 model/input_tokens/output_tokens/total_tokens
44
+ - dict 包含 model/input_tokens/output_tokens/total_tokens/request_id/provider
38
45
  """
39
46
 
40
47
  if not isinstance(payload, dict):
41
- return {"model": None, "input_tokens": None, "output_tokens": None, "total_tokens": None}
48
+ return {
49
+ "model": None,
50
+ "input_tokens": None,
51
+ "output_tokens": None,
52
+ "total_tokens": None,
53
+ "request_id": None,
54
+ "provider": None,
55
+ }
42
56
 
43
57
  usage_raw = payload.get("usage")
44
58
  usage_dict: Dict[str, Any] = usage_raw if isinstance(usage_raw, dict) else payload
45
- model = payload.get("model")
46
- if not isinstance(model, str) or not model.strip():
47
- model = usage_dict.get("model")
48
- model_text = model.strip() if isinstance(model, str) and model.strip() else None
59
+ model_text = _usage_text(payload.get("model")) or _usage_text(usage_dict.get("model"))
60
+ request_id = (
61
+ _usage_text(payload.get("request_id"))
62
+ or _usage_text(payload.get("id"))
63
+ or _usage_text(usage_dict.get("request_id"))
64
+ or _usage_text(usage_dict.get("id"))
65
+ )
66
+ provider = _usage_text(payload.get("provider")) or _usage_text(usage_dict.get("provider"))
49
67
 
50
68
  input_tokens = _usage_int(usage_dict.get("input_tokens"))
51
69
  if input_tokens is None:
@@ -62,4 +80,6 @@ def extract_usage_metrics(payload: Any) -> Dict[str, Optional[Any]]:
62
80
  "input_tokens": input_tokens,
63
81
  "output_tokens": output_tokens,
64
82
  "total_tokens": total_tokens,
83
+ "request_id": request_id,
84
+ "provider": provider,
65
85
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capability-runtime
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events).
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
 
3
+ from capability_runtime import AgentSpec, CapabilityKind, CapabilitySpec, Runtime, RuntimeConfig
3
4
  from capability_runtime.adapters.agently_backend import AgentlyBackendConfig, AgentlyChatBackend
4
5
  from skills_runtime.llm.protocol import ChatRequest
5
6
 
@@ -77,7 +78,7 @@ async def test_agently_backend_reports_usage_via_caprt_usage_sink():
77
78
  ("message", '{"choices":[{"delta":{"content":"ok"},"finish_reason":null}]}'),
78
79
  (
79
80
  "message",
80
- '{"choices":[{"delta":{},"finish_reason":"stop"}],"model":"bridge-usage-model","usage":{"prompt_tokens":11,"completion_tokens":7,"total_tokens":18}}',
81
+ '{"id":"req_123","provider":"openai-compatible","choices":[{"delta":{},"finish_reason":"stop"}],"model":"bridge-usage-model","usage":{"prompt_tokens":11,"completion_tokens":7,"total_tokens":18}}',
81
82
  ),
82
83
  ("message", "[DONE]"),
83
84
  ]
@@ -102,10 +103,93 @@ async def test_agently_backend_reports_usage_via_caprt_usage_sink():
102
103
  "input_tokens": 11,
103
104
  "output_tokens": 7,
104
105
  "total_tokens": 18,
106
+ "request_id": "req_123",
107
+ "provider": "openai-compatible",
105
108
  }
106
109
  ]
107
110
 
108
111
 
112
+ @pytest.mark.asyncio
113
+ async def test_agently_backend_usage_sink_accepts_missing_request_metadata():
114
+ usage_events = []
115
+ backend = _backend_from_items(
116
+ [
117
+ (
118
+ "message",
119
+ '{"choices":[{"delta":{},"finish_reason":"stop"}],"model":"bridge-usage-model","usage":{"prompt_tokens":1,"completion_tokens":2,"total_tokens":3}}',
120
+ ),
121
+ ("message", "[DONE]"),
122
+ ]
123
+ )
124
+
125
+ out = [
126
+ ev
127
+ async for ev in backend.stream_chat(
128
+ ChatRequest(
129
+ model="m",
130
+ messages=[{"role": "user", "content": "x"}],
131
+ tools=[],
132
+ extra={"_caprt_usage_sink": usage_events.append},
133
+ )
134
+ )
135
+ ]
136
+
137
+ assert [e.type for e in out] == ["completed"]
138
+ assert usage_events == [
139
+ {
140
+ "model": "bridge-usage-model",
141
+ "input_tokens": 1,
142
+ "output_tokens": 2,
143
+ "total_tokens": 3,
144
+ "request_id": None,
145
+ "provider": None,
146
+ }
147
+ ]
148
+
149
+
150
+ @pytest.mark.asyncio
151
+ async def test_agently_sse_usage_request_metadata_reaches_runtime_node_report(tmp_path):
152
+ backend = _backend_from_items(
153
+ [
154
+ ("message", '{"choices":[{"delta":{"content":"ok"},"finish_reason":null}]}'),
155
+ (
156
+ "message",
157
+ '{"id":"req_123","choices":[{"delta":{},"finish_reason":"stop"}],"model":"bridge-usage-model","usage":{"prompt_tokens":11,"completion_tokens":7,"total_tokens":18}}',
158
+ ),
159
+ ("message", "[DONE]"),
160
+ ]
161
+ )
162
+ rt = Runtime(
163
+ RuntimeConfig(
164
+ mode="sdk_native",
165
+ workspace_root=tmp_path,
166
+ sdk_config_paths=[],
167
+ preflight_mode="off",
168
+ sdk_backend=backend,
169
+ )
170
+ )
171
+ rt.register(
172
+ AgentSpec(
173
+ base=CapabilitySpec(
174
+ id="agent.sse_usage_metadata",
175
+ kind=CapabilityKind.AGENT,
176
+ name="SSEUsageMetadata",
177
+ description="离线:SSE completed usage metadata 透传。",
178
+ ),
179
+ )
180
+ )
181
+
182
+ result = await rt.run("agent.sse_usage_metadata", input={"prompt": "x"})
183
+
184
+ assert result.node_report is not None
185
+ assert result.node_report.usage is not None
186
+ assert result.node_report.usage.input_tokens == 11
187
+ assert result.node_report.usage.output_tokens == 7
188
+ assert result.node_report.usage.total_tokens == 18
189
+ assert result.node_report.usage.request_id == "req_123"
190
+ assert result.node_report.usage.provider == "openai"
191
+
192
+
109
193
  @pytest.mark.asyncio
110
194
  async def test_agently_backend_requests_include_usage_by_default_and_preserves_existing_stream_options():
111
195
  requester = _FakeRequester(
@@ -55,6 +55,59 @@ def test_report_aggregates_llm_usage_summary() -> None:
55
55
  assert rep.usage.total_tokens == 23
56
56
 
57
57
 
58
+ def test_report_preserves_last_llm_usage_request_metadata() -> None:
59
+ events = [
60
+ _ev("run_started"),
61
+ _ev(
62
+ "llm_usage",
63
+ payload={
64
+ "model": "gpt-4.1-mini",
65
+ "input_tokens": 11,
66
+ "output_tokens": 7,
67
+ "total_tokens": 18,
68
+ "request_id": "req_old",
69
+ "provider": "gateway-a",
70
+ },
71
+ ),
72
+ _ev(
73
+ "llm_usage",
74
+ payload={
75
+ "model": "gpt-4.1-mini",
76
+ "prompt_tokens": 2,
77
+ "completion_tokens": 3,
78
+ "total_tokens": 5,
79
+ "request_id": "req_123",
80
+ "provider": "gateway-b",
81
+ },
82
+ ),
83
+ _ev("run_completed", payload={"final_output": "ok", "wal_locator": "wal.jsonl"}),
84
+ ]
85
+
86
+ rep = NodeReportBuilder().build(events=events)
87
+
88
+ assert rep.usage is not None
89
+ assert rep.usage.input_tokens == 13
90
+ assert rep.usage.output_tokens == 10
91
+ assert rep.usage.total_tokens == 23
92
+ assert rep.usage.request_id == "req_123"
93
+ assert rep.usage.provider == "gateway-b"
94
+
95
+
96
+ def test_report_accepts_llm_usage_without_request_id() -> None:
97
+ events = [
98
+ _ev("run_started"),
99
+ _ev("llm_usage", payload={"model": "gpt-4.1-mini", "input_tokens": 1, "output_tokens": 2, "total_tokens": 3}),
100
+ _ev("run_completed", payload={"final_output": "ok", "wal_locator": "wal.jsonl"}),
101
+ ]
102
+
103
+ rep = NodeReportBuilder().build(events=events)
104
+
105
+ assert rep.usage is not None
106
+ assert rep.usage.total_tokens == 3
107
+ assert rep.usage.request_id is None
108
+ assert rep.usage.provider is None
109
+
110
+
58
111
  def test_report_collects_artifacts_from_run_completed_payload():
59
112
  events = [
60
113
  _ev("run_started"),
@@ -89,6 +89,18 @@ def test_node_usage_report_extra_fields_are_rejected() -> None:
89
89
  with pytest.raises(ValidationError):
90
90
  NodeUsageReport.model_validate({"model": "m", "total_tokens": 1, "unexpected": True})
91
91
 
92
+ ok = NodeUsageReport.model_validate(
93
+ {"model": "m", "total_tokens": 1, "request_id": "req_123", "provider": "openai-compatible"}
94
+ )
95
+ assert set(ok.model_dump().keys()) == {
96
+ "model",
97
+ "input_tokens",
98
+ "output_tokens",
99
+ "total_tokens",
100
+ "request_id",
101
+ "provider",
102
+ }
103
+
92
104
 
93
105
  def test_events_path_must_come_from_terminal_run_event() -> None:
94
106
  # 非终态事件携带 wal_locator/events_path 不应被采纳;只有 run_* 终态事件可提供 locator。
@@ -22,11 +22,11 @@ def _load_release_guard_module():
22
22
  def test_release_guard_accepts_current_tag() -> None:
23
23
  module = _load_release_guard_module()
24
24
  tag_version, pyproject_version, module_version = module.validate_versions(
25
- release_tag="v0.1.1",
25
+ release_tag="v0.1.2",
26
26
  pyproject_path=_REPO_ROOT / "pyproject.toml",
27
27
  init_path=_REPO_ROOT / "src" / "capability_runtime" / "__init__.py",
28
28
  )
29
- assert tag_version == pyproject_version == module_version == "0.1.1"
29
+ assert tag_version == pyproject_version == module_version == "0.1.2"
30
30
 
31
31
 
32
32
  def test_release_guard_rejects_mismatch() -> None: