capability-runtime 0.1.0__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.0 → capability_runtime-0.1.2}/PKG-INFO +2 -2
  2. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/pyproject.toml +2 -2
  3. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/__init__.py +3 -2
  4. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/adapters/agent_adapter.py +294 -5
  5. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/adapters/agently_backend.py +19 -3
  6. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/protocol/__init__.py +2 -1
  7. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/protocol/agent.py +8 -1
  8. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/reporting/node_report.py +20 -1
  9. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/runtime.py +16 -3
  10. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/sdk_lifecycle.py +100 -4
  11. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/services.py +10 -2
  12. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/types.py +3 -0
  13. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/utils/usage.py +26 -6
  14. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/PKG-INFO +2 -2
  15. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/SOURCES.txt +1 -0
  16. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/requires.txt +1 -1
  17. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_agently_backend.py +85 -1
  18. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_dependency_pins.py +2 -1
  19. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_node_report_builder.py +53 -0
  20. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_node_report_contract_v1.py +12 -0
  21. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_per_capability_llm_config_model_routing.py +131 -1
  22. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_public_api_exports.py +1 -0
  23. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_release_tag_version_guardrail.py +2 -2
  24. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_initial_history_and_meta.py +47 -0
  25. capability_runtime-0.1.2/tests/test_upstream_prompt_profile_contract.py +101 -0
  26. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/README.md +0 -0
  27. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/setup.cfg +0 -0
  28. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/adapters/__init__.py +0 -0
  29. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/adapters/triggerflow_workflow_engine.py +0 -0
  30. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/adapters/workflow_engine.py +0 -0
  31. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/config.py +0 -0
  32. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/errors.py +0 -0
  33. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/guards.py +0 -0
  34. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_protocol.py +0 -0
  35. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/__init__.py +0 -0
  36. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/approvals_profiles.py +0 -0
  37. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/evidence_hooks.py +0 -0
  38. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/history.py +0 -0
  39. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/invoke_capability.py +0 -0
  40. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/resume.py +0 -0
  41. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/system_prompt.py +0 -0
  42. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/host_toolkit/turn_delta.py +0 -0
  43. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/logging_utils.py +0 -0
  44. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/manifest.py +0 -0
  45. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/output_validator.py +0 -0
  46. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/protocol/capability.py +0 -0
  47. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/protocol/chat_backend.py +0 -0
  48. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/protocol/context.py +0 -0
  49. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/protocol/workflow.py +0 -0
  50. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/registry.py +0 -0
  51. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/reporting/__init__.py +0 -0
  52. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/runtime_ui_events_mixin.py +0 -0
  53. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/service_facade.py +0 -0
  54. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/structured_output.py +0 -0
  55. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/structured_stream.py +0 -0
  56. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/__init__.py +0 -0
  57. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/projector.py +0 -0
  58. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/session.py +0 -0
  59. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/store.py +0 -0
  60. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/transport.py +0 -0
  61. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/ui_events/v1.py +0 -0
  62. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/upstream_compat.py +0 -0
  63. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/utils/__init__.py +0 -0
  64. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime/workflow_runtime.py +0 -0
  65. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/dependency_links.txt +0 -0
  66. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/src/capability_runtime.egg-info/top_level.txt +0 -0
  67. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_agently_backend_replay.py +0 -0
  68. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_backend_mode.py +0 -0
  69. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_bilingual_docs_surface.py +0 -0
  70. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_bridge_artifacts_passthrough.py +0 -0
  71. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_bridge_register_tool_public_api.py +0 -0
  72. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_coding_agent_examples_atomic.py +0 -0
  73. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_coding_agent_examples_recipes.py +0 -0
  74. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_config_glue.py +0 -0
  75. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_docs_pinned_dependency_versions.py +0 -0
  76. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_docs_scheme2_no_skilladapter_residue.py +0 -0
  77. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_error_observability.py +0 -0
  78. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_alignment_fixes_l1.py +0 -0
  79. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_no_agent_sdk_imports.py +0 -0
  80. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_real_evidence_strict_integration.py +0 -0
  81. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_real_integration.py +0 -0
  82. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_smoke.py +0 -0
  83. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_offline.py +0 -0
  84. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_real_fallback.py +0 -0
  85. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_real_integration.py +0 -0
  86. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_ui_events_showcase_ui_contract.py +0 -0
  87. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_examples_workflow_skills_first_smoke.py +0 -0
  88. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_guards.py +0 -0
  89. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_approvals_profiles.py +0 -0
  90. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_history_assembler.py +0 -0
  91. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_invoke_capability.py +0 -0
  92. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_invoke_capability_shared_runtime.py +0 -0
  93. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_resume_helper.py +0 -0
  94. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_system_prompt_evidence.py +0 -0
  95. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_host_toolkit_turn_delta.py +0 -0
  96. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_integration_agently_requester_smoke.py +0 -0
  97. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_integration_approval_event_shape.py +0 -0
  98. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_integration_sources_redis_pgsql_smoke.py +0 -0
  99. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_loop.py +0 -0
  100. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_node_report_engine_identity_contract.py +0 -0
  101. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_offline_backend_injection_evidence.py +0 -0
  102. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_preflight_gate.py +0 -0
  103. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_project_identity_naming_matrix.py +0 -0
  104. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_public_repo_hygiene.py +0 -0
  105. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_qa_agent.py +0 -0
  106. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_registry.py +0 -0
  107. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_replay_tool_calls_alignment.py +0 -0
  108. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_repo_hygiene_no_tracked_env_or_pyc.py +0 -0
  109. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_repo_no_deep_imports_in_user_facing_docs.py +0 -0
  110. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_review_followups_module_contracts.py +0 -0
  111. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_bridge_fake_backend.py +0 -0
  112. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_concurrency.py +0 -0
  113. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_engine.py +0 -0
  114. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_hitl_host_protocol.py +0 -0
  115. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_hooks_and_schema_gate.py +0 -0
  116. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_manifest.py +0 -0
  117. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_run_stream_semantics_v1.py +0 -0
  118. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_service_facade_rpc_mapping.py +0 -0
  119. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_service_session_bridge.py +0 -0
  120. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_status_mapping.py +0 -0
  121. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_structured_output_bridge.py +0 -0
  122. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_runtime_structured_stream.py +0 -0
  123. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_sandbox_permissions_passthrough_contract.py +0 -0
  124. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_services_call_callback.py +0 -0
  125. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_services_map_node_status.py +0 -0
  126. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_skills_conformance_smoke.py +0 -0
  127. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_adapter_tool_registration.py +0 -0
  128. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_chat_backend_protocol_contract.py +0 -0
  129. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_chat_sse_usage_contract.py +0 -0
  130. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_compat_spaces_schema.py +0 -0
  131. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_runtime_client_server_contract.py +0 -0
  132. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_sandbox_profile_precedence_contract.py +0 -0
  133. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_skills_bundles_contract.py +0 -0
  134. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_tool_descriptor_compat.py +0 -0
  135. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_upstream_verification.py +0 -0
  136. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_versioning_strategy_guard.py +0 -0
  137. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_wal_backend_injection_contract.py +0 -0
  138. {capability_runtime-0.1.0 → capability_runtime-0.1.2}/tests/test_wal_locator_resolution_contract.py +0 -0
  139. {capability_runtime-0.1.0 → 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.0
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
@@ -8,7 +8,7 @@ Description-Content-Type: text/markdown
8
8
  Requires-Dist: PyYAML>=6
9
9
  Requires-Dist: pydantic<3,>=2
10
10
  Requires-Dist: agently==4.0.8
11
- Requires-Dist: skills-runtime-sdk==0.1.11
11
+ Requires-Dist: skills-runtime-sdk==0.1.12
12
12
  Provides-Extra: dev
13
13
  Requires-Dist: pytest>=7; extra == "dev"
14
14
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capability-runtime"
3
- version = "0.1.0"
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"
@@ -9,7 +9,7 @@ dependencies = [
9
9
  "PyYAML>=6",
10
10
  "pydantic>=2,<3",
11
11
  "agently==4.0.8",
12
- "skills-runtime-sdk==0.1.11",
12
+ "skills-runtime-sdk==0.1.12",
13
13
  ]
14
14
 
15
15
  [project.optional-dependencies]
@@ -1,7 +1,7 @@
1
1
  """capability-runtime:统一 Runtime 入口(能力协议 + 执行 + 报告)。"""
2
2
  from __future__ import annotations
3
3
 
4
- __version__ = "0.1.0"
4
+ __version__ = "0.1.2"
5
5
 
6
6
  # === 统一入口 ===
7
7
  from .config import CustomTool, RuntimeConfig
@@ -18,7 +18,7 @@ from .host_protocol import ApprovalTicket, HostRunSnapshot, HostRunStatus, Resum
18
18
  from .manifest import CapabilityDescriptor, CapabilityManifestEntry, CapabilityVisibility
19
19
 
20
20
  # === Protocol 导出 ===
21
- from .protocol.agent import AgentIOSchema, AgentSpec
21
+ from .protocol.agent import AgentIOSchema, AgentSpec, PromptRenderMode
22
22
  from .protocol.capability import (
23
23
  CapabilityKind,
24
24
  CapabilityRef,
@@ -76,6 +76,7 @@ __all__ = [
76
76
  "RuntimeServiceFacade",
77
77
  "AgentSpec",
78
78
  "AgentIOSchema",
79
+ "PromptRenderMode",
79
80
  "WorkflowSpec",
80
81
  "Step",
81
82
  "LoopStep",
@@ -10,8 +10,11 @@ AgentAdapter:AgentSpec 的执行适配器(统一 mock/bridge/sdk_native)
10
10
  from __future__ import annotations
11
11
 
12
12
  import asyncio
13
+ import hashlib
13
14
  import inspect
14
15
  import json
16
+ import re
17
+ from dataclasses import dataclass
15
18
  from typing import Any, AsyncIterator, Dict, List, Optional, Union
16
19
 
17
20
  from skills_runtime.core.contracts import AgentEvent
@@ -30,6 +33,34 @@ _SECTION_TASK = "## 任务"
30
33
  _SECTION_INPUT = "## 输入"
31
34
  _SECTION_OUTPUT_PREFIX = "## 输出要求"
32
35
  _SECTION_SKILLS = "## 使用以下 Skills"
36
+ _RUNTIME_PROMPT_KEY = "_runtime_prompt"
37
+ _PROMPT_HASH_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
38
+ _ALLOWED_PROMPT_ROLES = {"system", "developer", "user", "assistant", "tool"}
39
+ _ALLOWED_PROMPT_PROFILES = {"default_agent", "generation_direct", "structured_transform"}
40
+
41
+
42
+ class _InvalidPromptMessages(ValueError):
43
+ """prompt control envelope 非法。"""
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class _PromptRenderPlan:
48
+ """
49
+ AgentAdapter 内部 prompt 渲染计划。
50
+
51
+ 字段:
52
+ - mode:三种 prompt rendering strategy 之一;
53
+ - task:传给 SDK Agent.run_stream_async 的 task 文本;
54
+ - prompt_profile:透传给上游 SDK 的 prompt profile;
55
+ - precomposed_messages:最终 provider messages 覆写;
56
+ - evidence:写入 NodeReport.meta 的最小披露摘要。
57
+ """
58
+
59
+ mode: str
60
+ task: str
61
+ prompt_profile: Optional[str]
62
+ precomposed_messages: Optional[List[Dict[str, Any]]]
63
+ evidence: Dict[str, Any]
33
64
 
34
65
 
35
66
  class AgentAdapter:
@@ -168,6 +199,29 @@ class AgentAdapter:
168
199
  真实执行(bridge/sdk_native):驱动 SDK Agent.run_stream_async 并聚合 NodeReport。
169
200
  """
170
201
 
202
+ try:
203
+ prompt_plan = self._resolve_prompt_render_plan(spec=spec, input=input)
204
+ except _InvalidPromptMessages as exc:
205
+ report = self._services.build_fail_closed_report(
206
+ run_id=context.run_id,
207
+ status="failed",
208
+ reason="invalid_prompt_messages",
209
+ completion_reason="invalid_prompt_messages",
210
+ meta={
211
+ "capability_id": spec.base.id,
212
+ "source": "prompt_rendering",
213
+ "prompt_error": str(exc),
214
+ },
215
+ )
216
+ yield CapabilityResult(
217
+ status=CapabilityStatus.FAILED,
218
+ error=str(exc),
219
+ error_code="INVALID_PROMPT_MESSAGES",
220
+ report=report,
221
+ node_report=report,
222
+ )
223
+ return
224
+
171
225
  # preflight gate(生产默认 fail-closed)
172
226
  issues: List[FrameworkIssue] = []
173
227
  if getattr(self._services.config, "preflight_mode", "error") != "off":
@@ -184,6 +238,7 @@ class AgentAdapter:
184
238
  "code": "SKILL_PREFLIGHT_FAILED",
185
239
  "details": {"issues": [self._services.redact_issue(i) for i in issues]},
186
240
  },
241
+ **prompt_plan.evidence,
187
242
  },
188
243
  )
189
244
  yield CapabilityResult(
@@ -196,8 +251,34 @@ class AgentAdapter:
196
251
  )
197
252
  return
198
253
 
199
- task = self._build_task(spec=spec, input=input)
200
- agent = self._services.create_sdk_agent(llm_config=spec.llm_config)
254
+ task = prompt_plan.task
255
+ create_agent_kwargs: Dict[str, Any] = {"llm_config": spec.llm_config}
256
+ if prompt_plan.prompt_profile is not None:
257
+ create_agent_kwargs["prompt_profile"] = prompt_plan.prompt_profile
258
+ if prompt_plan.precomposed_messages is not None:
259
+ create_agent_kwargs["precomposed_messages"] = prompt_plan.precomposed_messages
260
+ try:
261
+ agent = self._services.create_sdk_agent(**create_agent_kwargs)
262
+ except Exception as exc:
263
+ report = self._services.build_fail_closed_report(
264
+ run_id=context.run_id,
265
+ status="failed",
266
+ reason="engine_error",
267
+ completion_reason="engine_exception",
268
+ meta={
269
+ "source": "sdk_agent_create",
270
+ "engine_exception": type(exc).__name__,
271
+ **prompt_plan.evidence,
272
+ },
273
+ )
274
+ yield CapabilityResult(
275
+ status=CapabilityStatus.FAILED,
276
+ error=str(exc),
277
+ error_code="ENGINE_ERROR",
278
+ report=report,
279
+ node_report=report,
280
+ )
281
+ return
201
282
 
202
283
  events: List[AgentEvent] = []
203
284
  host_meta = self._services.get_host_meta(context=context)
@@ -241,6 +322,7 @@ class AgentAdapter:
241
322
  completion_reason="run_cancelled",
242
323
  meta={"capability_id": spec.base.id, "source": "sdk_agent"},
243
324
  )
325
+ self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
244
326
  yield CapabilityResult(
245
327
  status=CapabilityStatus.CANCELLED,
246
328
  error="execution cancelled",
@@ -255,7 +337,7 @@ class AgentAdapter:
255
337
  status="failed",
256
338
  reason="engine_error",
257
339
  completion_reason="engine_exception",
258
- meta={"engine_exception": type(exc).__name__},
340
+ meta={"engine_exception": type(exc).__name__, **prompt_plan.evidence},
259
341
  )
260
342
  yield CapabilityResult(
261
343
  status=CapabilityStatus.FAILED,
@@ -267,6 +349,7 @@ class AgentAdapter:
267
349
  return
268
350
 
269
351
  report = NodeReportBuilder().build(events=events)
352
+ self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
270
353
  if issues and getattr(self._services.config, "preflight_mode", "error") == "warn":
271
354
  report.meta["preflight_mode"] = "warn"
272
355
  report.meta["preflight_issues"] = [self._services.redact_issue(i) for i in issues]
@@ -306,6 +389,166 @@ class AgentAdapter:
306
389
  artifacts=list(report.artifacts),
307
390
  )
308
391
 
392
+ def _resolve_prompt_render_plan(self, *, spec: AgentSpec, input: Dict[str, Any]) -> _PromptRenderPlan:
393
+ """
394
+ 解析 AgentSpec + run-level `_runtime_prompt`,生成 prompt 渲染计划。
395
+
396
+ 参数:
397
+ - spec:Agent 声明
398
+ - input:run 输入,其中 `_runtime_prompt` 为保留控制面
399
+
400
+ 返回:
401
+ - `_PromptRenderPlan`
402
+
403
+ 异常:
404
+ - `_InvalidPromptMessages`:控制面非法,调用方应 fail-fast。
405
+ """
406
+
407
+ envelope_raw = input.get(_RUNTIME_PROMPT_KEY)
408
+ if envelope_raw is None:
409
+ envelope: Dict[str, Any] = {}
410
+ elif isinstance(envelope_raw, dict):
411
+ envelope = dict(envelope_raw)
412
+ else:
413
+ raise _InvalidPromptMessages("_runtime_prompt must be a dict")
414
+
415
+ raw_mode = envelope.get("mode") if "mode" in envelope else getattr(spec, "prompt_render_mode", "structured_task")
416
+ if raw_mode is None:
417
+ raw_mode = "structured_task"
418
+ if not isinstance(raw_mode, str) or not raw_mode.strip():
419
+ raise _InvalidPromptMessages("prompt render mode must be a non-empty string")
420
+ mode = raw_mode.strip()
421
+ if mode not in {"structured_task", "direct_task_text", "precomposed_messages"}:
422
+ raise _InvalidPromptMessages(
423
+ "unsupported prompt render mode; allowed: direct_task_text, precomposed_messages, structured_task"
424
+ )
425
+
426
+ prompt_profile = envelope.get("profile")
427
+ if prompt_profile is None:
428
+ prompt_profile = getattr(spec, "prompt_profile", None)
429
+ if prompt_profile is not None:
430
+ if not isinstance(prompt_profile, str) or not prompt_profile.strip():
431
+ raise _InvalidPromptMessages("prompt profile must be a non-empty string")
432
+ prompt_profile = prompt_profile.strip()
433
+ if prompt_profile not in _ALLOWED_PROMPT_PROFILES:
434
+ allowed = ", ".join(sorted(_ALLOWED_PROMPT_PROFILES))
435
+ raise _InvalidPromptMessages(f"unsupported prompt profile; allowed: {allowed}")
436
+
437
+ trace = envelope.get("trace") if "trace" in envelope else {}
438
+ if trace is None:
439
+ trace = {}
440
+ if not isinstance(trace, dict):
441
+ raise _InvalidPromptMessages("_runtime_prompt.trace must be a dict")
442
+ prompt_hash = trace.get("prompt_hash")
443
+ if prompt_hash is not None:
444
+ if not isinstance(prompt_hash, str) or _PROMPT_HASH_RE.fullmatch(prompt_hash.strip()) is None:
445
+ raise _InvalidPromptMessages("trace.prompt_hash must match sha256:<64 lowercase hex>")
446
+ prompt_hash = prompt_hash.strip()
447
+ composer_version = trace.get("composer_version")
448
+ if composer_version is not None and not isinstance(composer_version, str):
449
+ raise _InvalidPromptMessages("trace.composer_version must be a string")
450
+
451
+ if mode == "structured_task":
452
+ business_input = self._strip_runtime_prompt(input)
453
+ task = self._build_task(spec=spec, input=business_input)
454
+ evidence = self._build_prompt_evidence(
455
+ mode=mode,
456
+ prompt_profile=prompt_profile,
457
+ prompt_hash=prompt_hash or _hash_text(task),
458
+ composer_version=composer_version,
459
+ messages=None,
460
+ )
461
+ return _PromptRenderPlan(
462
+ mode=mode,
463
+ task=task,
464
+ prompt_profile=prompt_profile,
465
+ precomposed_messages=None,
466
+ evidence=evidence,
467
+ )
468
+
469
+ if mode == "direct_task_text":
470
+ task_text = envelope.get("task_text")
471
+ if not isinstance(task_text, str) or not task_text.strip():
472
+ raise _InvalidPromptMessages("_runtime_prompt.task_text must be a non-empty string")
473
+ task = task_text
474
+ evidence = self._build_prompt_evidence(
475
+ mode=mode,
476
+ prompt_profile=prompt_profile,
477
+ prompt_hash=prompt_hash or _hash_text(task),
478
+ composer_version=composer_version,
479
+ messages=None,
480
+ )
481
+ return _PromptRenderPlan(
482
+ mode=mode,
483
+ task=task,
484
+ prompt_profile=prompt_profile,
485
+ precomposed_messages=None,
486
+ evidence=evidence,
487
+ )
488
+
489
+ messages = _validate_precomposed_messages(envelope.get("messages"))
490
+ evidence = self._build_prompt_evidence(
491
+ mode=mode,
492
+ prompt_profile=prompt_profile,
493
+ prompt_hash=prompt_hash or _hash_messages(messages),
494
+ composer_version=composer_version,
495
+ messages=messages,
496
+ )
497
+ return _PromptRenderPlan(
498
+ mode=mode,
499
+ task="",
500
+ prompt_profile=prompt_profile,
501
+ precomposed_messages=messages,
502
+ evidence=evidence,
503
+ )
504
+
505
+ def _build_prompt_evidence(
506
+ self,
507
+ *,
508
+ mode: str,
509
+ prompt_profile: Optional[str],
510
+ prompt_hash: str,
511
+ composer_version: Any,
512
+ messages: Optional[List[Dict[str, Any]]],
513
+ ) -> Dict[str, Any]:
514
+ """
515
+ 构造 NodeReport.meta 的 prompt 最小披露摘要。
516
+
517
+ 参数:
518
+ - mode:prompt render mode
519
+ - prompt_profile:可选 SDK prompt profile
520
+ - prompt_hash:`sha256:<hex>` 摘要
521
+ - composer_version:可选 composer 版本
522
+ - messages:precomposed messages;仅用于 count/roles 摘要
523
+ """
524
+
525
+ evidence: Dict[str, Any] = {
526
+ "prompt_render_mode": mode,
527
+ "prompt_hash": prompt_hash,
528
+ }
529
+ if prompt_profile:
530
+ evidence["prompt_profile"] = prompt_profile
531
+ if isinstance(composer_version, str) and composer_version:
532
+ evidence["prompt_composer_version"] = composer_version
533
+ if messages is not None:
534
+ evidence["prompt_messages_count"] = len(messages)
535
+ evidence["prompt_message_roles"] = [str(item["role"]) for item in messages]
536
+ return evidence
537
+
538
+ def _strip_runtime_prompt(self, input: Dict[str, Any]) -> Dict[str, Any]:
539
+ """从业务 input 中剥离 runtime prompt 控制面。"""
540
+
541
+ if _RUNTIME_PROMPT_KEY not in input:
542
+ return input
543
+ return {k: v for k, v in input.items() if k != _RUNTIME_PROMPT_KEY}
544
+
545
+ def _apply_prompt_evidence(self, *, report: Any, evidence: Dict[str, Any]) -> None:
546
+ """把 prompt evidence 写入 NodeReport.meta;兼容测试中的 dict fake report。"""
547
+
548
+ meta = getattr(report, "meta", None)
549
+ if isinstance(meta, dict):
550
+ meta.update(evidence)
551
+
309
552
  def _build_task(self, *, spec: AgentSpec, input: Dict[str, Any]) -> str:
310
553
  """
311
554
  将 AgentSpec + input 转换为 SDK Agent 的 task 文本(结构化拼接)。
@@ -323,9 +566,10 @@ class AgentAdapter:
323
566
  if spec.base.description:
324
567
  parts.append(f"{_SECTION_TASK}\n{spec.base.description}")
325
568
 
326
- if input:
569
+ business_input = self._strip_runtime_prompt(input)
570
+ if business_input:
327
571
  lines: List[str] = []
328
- for k, v in input.items():
572
+ for k, v in business_input.items():
329
573
  if isinstance(v, str):
330
574
  lines.append(f"- {k}: {v}")
331
575
  else:
@@ -437,3 +681,48 @@ class AgentAdapter:
437
681
  if account and domain:
438
682
  return f"[{account}:{domain}]"
439
683
  return None
684
+
685
+
686
+ def _hash_text(text: str) -> str:
687
+ """生成 prompt 文本的 sha256 摘要。"""
688
+
689
+ return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
690
+
691
+
692
+ def _hash_messages(messages: List[Dict[str, Any]]) -> str:
693
+ """对 provider messages 做稳定 JSON canonicalization 后生成摘要。"""
694
+
695
+ canonical = json.dumps(messages, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
696
+ return _hash_text(canonical)
697
+
698
+
699
+ def _validate_precomposed_messages(raw: Any) -> List[Dict[str, Any]]:
700
+ """
701
+ 校验 host 提供的最终 provider messages。
702
+
703
+ 约束:
704
+ - 只接受非空 list[dict];
705
+ - 每条消息必须有合法 role 与字符串 content;
706
+ - 返回浅拷贝,避免后续执行链路修改调用方输入。
707
+ """
708
+
709
+ if not isinstance(raw, list):
710
+ raise _InvalidPromptMessages("_runtime_prompt.messages must be a list")
711
+ if not raw:
712
+ raise _InvalidPromptMessages("_runtime_prompt.messages must not be empty")
713
+
714
+ out: List[Dict[str, Any]] = []
715
+ for idx, item in enumerate(raw):
716
+ if not isinstance(item, dict):
717
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}] must be a dict")
718
+ role = item.get("role")
719
+ content = item.get("content")
720
+ if not isinstance(role, str) or role not in _ALLOWED_PROMPT_ROLES:
721
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].role is invalid")
722
+ if not isinstance(content, str):
723
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].content must be a string")
724
+ copied = dict(item)
725
+ copied["role"] = role
726
+ copied["content"] = content
727
+ out.append(copied)
728
+ return out
@@ -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]:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  """Protocol 层:纯能力类型定义,不依赖任何上游模块。"""
4
4
 
5
- from .agent import AgentIOSchema, AgentSpec
5
+ from .agent import AgentIOSchema, AgentSpec, PromptRenderMode
6
6
  from .capability import (
7
7
  CapabilityKind,
8
8
  CapabilityRef,
@@ -30,6 +30,7 @@ __all__ = [
30
30
  "CapabilityResult",
31
31
  "AgentSpec",
32
32
  "AgentIOSchema",
33
+ "PromptRenderMode",
33
34
  "ChatBackendProtocol",
34
35
  "WorkflowSpec",
35
36
  "Step",
@@ -3,11 +3,14 @@ from __future__ import annotations
3
3
  """Agent 元能力声明。"""
4
4
 
5
5
  from dataclasses import dataclass, field
6
- from typing import Any, Dict, List, Optional
6
+ from typing import Any, Dict, List, Literal, Optional
7
7
 
8
8
  from .capability import CapabilityRef, CapabilitySpec
9
9
 
10
10
 
11
+ PromptRenderMode = Literal["structured_task", "direct_task_text", "precomposed_messages"]
12
+
13
+
11
14
  @dataclass(frozen=True)
12
15
  class AgentIOSchema:
13
16
  """
@@ -36,6 +39,8 @@ class AgentSpec:
36
39
  - output_schema: 输出 schema(可选)
37
40
  - loop_compatible: 是否可被 LoopStep 循环调用
38
41
  - llm_config: LLM 覆盖配置
42
+ - prompt_render_mode: prompt 渲染模式(默认 structured_task,保持兼容)
43
+ - prompt_profile: 可选上游 SDK prompt profile(如 generation_direct)
39
44
  - prompt_template: 可选的 prompt 模板(支持 {field} 占位符)
40
45
  - system_prompt: 可选的“Agent 级 system message”(用于该 Agent 的提示词组织)
41
46
 
@@ -58,5 +63,7 @@ class AgentSpec:
58
63
  output_schema: Optional[AgentIOSchema] = None
59
64
  loop_compatible: bool = False
60
65
  llm_config: Optional[Dict[str, Any]] = None
66
+ prompt_render_mode: PromptRenderMode = "structured_task"
67
+ prompt_profile: Optional[str] = None
61
68
  prompt_template: Optional[str] = None
62
69
  system_prompt: Optional[str] = None
@@ -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(
@@ -914,17 +914,30 @@ class Runtime(RuntimeUIEventsMixin):
914
914
  return []
915
915
  return self._sdk.preflight()
916
916
 
917
- def create_sdk_agent(self, *, llm_config: Optional[Dict[str, Any]] = None) -> Any:
917
+ def create_sdk_agent(
918
+ self,
919
+ *,
920
+ llm_config: Optional[Dict[str, Any]] = None,
921
+ prompt_profile: Optional[str] = None,
922
+ precomposed_messages: Optional[List[Dict[str, Any]]] = None,
923
+ ) -> Any:
918
924
  """
919
925
  RuntimeServices 协议方法:创建 per-run SDK Agent。
920
926
 
921
927
  参数:
922
- - llm_config:可选 LLM 覆写配置(当前仅支持 `model` 字段覆写)
928
+ - llm_config:可选 LLM 覆写配置
929
+ - prompt_profile:可选 SDK prompt profile
930
+ - precomposed_messages:可选最终 provider messages 覆写
923
931
  """
924
932
 
925
933
  if self._sdk is None:
926
934
  raise RuntimeError("SDK lifecycle is not initialized")
927
- return self._sdk.create_agent(custom_tools=list(self._config.custom_tools), llm_config=llm_config)
935
+ return self._sdk.create_agent(
936
+ custom_tools=list(self._config.custom_tools),
937
+ llm_config=llm_config,
938
+ prompt_profile=prompt_profile,
939
+ precomposed_messages=precomposed_messages,
940
+ )
928
941
 
929
942
 
930
943
  __all__ = ["Runtime"]