capability-runtime 0.1.3.post1__tar.gz → 0.1.5__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 (162) hide show
  1. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/PKG-INFO +82 -7
  2. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/README.md +80 -5
  3. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/pyproject.toml +2 -2
  4. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/__init__.py +36 -4
  5. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/adapters/agent_adapter.py +182 -8
  6. capability_runtime-0.1.5/src/capability_runtime/adapters/agently_backend.py +1142 -0
  7. capability_runtime-0.1.5/src/capability_runtime/adapters/agently_compat.py +95 -0
  8. capability_runtime-0.1.5/src/capability_runtime/adapters/agently_workspace.py +19 -0
  9. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/adapters/triggerflow_workflow_engine.py +228 -22
  10. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/config.py +83 -14
  11. capability_runtime-0.1.5/src/capability_runtime/context_pack.py +290 -0
  12. capability_runtime-0.1.5/src/capability_runtime/dynamic_workflow.py +365 -0
  13. capability_runtime-0.1.5/src/capability_runtime/errors.py +67 -0
  14. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/protocol/__init__.py +3 -0
  15. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/protocol/chat_backend.py +5 -8
  16. capability_runtime-0.1.5/src/capability_runtime/protocol/dynamic_workflow.py +37 -0
  17. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/reporting/node_report.py +192 -1
  18. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/runtime.py +543 -4
  19. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/sdk_lifecycle.py +134 -42
  20. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/types.py +2 -0
  21. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/ui_events/projector.py +208 -8
  22. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/ui_events/v1.py +3 -0
  23. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/utils/usage.py +7 -2
  24. capability_runtime-0.1.5/src/capability_runtime/workflow_runtime.py +373 -0
  25. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime.egg-info/PKG-INFO +82 -7
  26. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime.egg-info/SOURCES.txt +14 -0
  27. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime.egg-info/requires.txt +1 -1
  28. capability_runtime-0.1.5/tests/test_agently_action_artifact_evidence.py +328 -0
  29. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_agently_backend.py +134 -3
  30. capability_runtime-0.1.5/tests/test_agently_backend_authorization_header_contract.py +45 -0
  31. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_agently_backend_replay.py +38 -0
  32. capability_runtime-0.1.5/tests/test_agently_bridge_diagnostics.py +120 -0
  33. capability_runtime-0.1.5/tests/test_agently_workspace_recall_preview.py +190 -0
  34. capability_runtime-0.1.5/tests/test_backend_mode.py +310 -0
  35. capability_runtime-0.1.5/tests/test_config_glue.py +85 -0
  36. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_dependency_pins.py +16 -0
  37. capability_runtime-0.1.5/tests/test_dynamic_workflow_plan_contract.py +117 -0
  38. capability_runtime-0.1.5/tests/test_dynamic_workflow_runtime.py +279 -0
  39. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_smoke.py +47 -1
  40. capability_runtime-0.1.5/tests/test_integration_agently_requester_smoke.py +82 -0
  41. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_node_report_builder.py +64 -0
  42. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_node_report_contract_v1.py +8 -1
  43. capability_runtime-0.1.5/tests/test_openai_responses_compatible_bridge.py +1277 -0
  44. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_per_capability_llm_config_model_routing.py +220 -0
  45. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_public_api_exports.py +43 -1
  46. capability_runtime-0.1.5/tests/test_publish_real_provider_gate.py +112 -0
  47. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_release_tag_version_guardrail.py +2 -2
  48. capability_runtime-0.1.5/tests/test_review_followups_module_contracts.py +298 -0
  49. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_run_stream_semantics_v1.py +137 -2
  50. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_structured_stream.py +107 -1
  51. capability_runtime-0.1.5/tests/test_triggerflow_lifecycle_mapping.py +265 -0
  52. capability_runtime-0.1.5/tests/test_workflow_host_runtime_surface.py +384 -0
  53. capability_runtime-0.1.3.post1/src/capability_runtime/adapters/agently_backend.py +0 -439
  54. capability_runtime-0.1.3.post1/src/capability_runtime/errors.py +0 -20
  55. capability_runtime-0.1.3.post1/src/capability_runtime/workflow_runtime.py +0 -218
  56. capability_runtime-0.1.3.post1/tests/test_backend_mode.py +0 -106
  57. capability_runtime-0.1.3.post1/tests/test_config_glue.py +0 -41
  58. capability_runtime-0.1.3.post1/tests/test_integration_agently_requester_smoke.py +0 -47
  59. capability_runtime-0.1.3.post1/tests/test_review_followups_module_contracts.py +0 -57
  60. capability_runtime-0.1.3.post1/tests/test_workflow_host_runtime_surface.py +0 -200
  61. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/setup.cfg +0 -0
  62. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/adapters/__init__.py +0 -0
  63. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/adapters/workflow_engine.py +0 -0
  64. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/guards.py +0 -0
  65. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_protocol.py +0 -0
  66. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/__init__.py +0 -0
  67. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/approvals_profiles.py +0 -0
  68. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/evidence_hooks.py +0 -0
  69. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/history.py +0 -0
  70. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/invoke_capability.py +0 -0
  71. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/resume.py +0 -0
  72. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/system_prompt.py +0 -0
  73. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/host_toolkit/turn_delta.py +0 -0
  74. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/logging_utils.py +0 -0
  75. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/manifest.py +0 -0
  76. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/output_validator.py +0 -0
  77. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/protocol/agent.py +0 -0
  78. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/protocol/capability.py +0 -0
  79. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/protocol/context.py +0 -0
  80. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/protocol/workflow.py +0 -0
  81. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/registry.py +0 -0
  82. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/reporting/__init__.py +0 -0
  83. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/runtime_ui_events_mixin.py +0 -0
  84. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/service_facade.py +0 -0
  85. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/services.py +0 -0
  86. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/structured_output.py +0 -0
  87. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/structured_stream.py +0 -0
  88. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/ui_events/__init__.py +0 -0
  89. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/ui_events/session.py +0 -0
  90. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/ui_events/store.py +0 -0
  91. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/ui_events/transport.py +0 -0
  92. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/upstream_compat.py +0 -0
  93. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime/utils/__init__.py +0 -0
  94. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime.egg-info/dependency_links.txt +0 -0
  95. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/src/capability_runtime.egg-info/top_level.txt +0 -0
  96. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_bilingual_docs_surface.py +0 -0
  97. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_bridge_artifacts_passthrough.py +0 -0
  98. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_bridge_register_tool_public_api.py +0 -0
  99. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_coding_agent_examples_atomic.py +0 -0
  100. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_coding_agent_examples_recipes.py +0 -0
  101. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_docs_pinned_dependency_versions.py +0 -0
  102. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_docs_scheme2_no_skilladapter_residue.py +0 -0
  103. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_error_observability.py +0 -0
  104. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_alignment_fixes_l1.py +0 -0
  105. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_no_agent_sdk_imports.py +0 -0
  106. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_real_evidence_strict_integration.py +0 -0
  107. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_real_integration.py +0 -0
  108. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_ui_events_showcase_offline.py +0 -0
  109. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_ui_events_showcase_real_fallback.py +0 -0
  110. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_ui_events_showcase_real_integration.py +0 -0
  111. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_ui_events_showcase_ui_contract.py +0 -0
  112. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_examples_workflow_skills_first_smoke.py +0 -0
  113. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_guards.py +0 -0
  114. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_approvals_profiles.py +0 -0
  115. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_history_assembler.py +0 -0
  116. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_invoke_capability.py +0 -0
  117. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_invoke_capability_shared_runtime.py +0 -0
  118. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_resume_helper.py +0 -0
  119. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_system_prompt_evidence.py +0 -0
  120. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_host_toolkit_turn_delta.py +0 -0
  121. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_integration_approval_event_shape.py +0 -0
  122. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_integration_sources_redis_pgsql_smoke.py +0 -0
  123. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_loop.py +0 -0
  124. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_node_report_engine_identity_contract.py +0 -0
  125. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_offline_backend_injection_evidence.py +0 -0
  126. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_preflight_gate.py +0 -0
  127. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_project_identity_naming_matrix.py +0 -0
  128. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_public_repo_hygiene.py +0 -0
  129. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_qa_agent.py +0 -0
  130. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_registry.py +0 -0
  131. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_replay_tool_calls_alignment.py +0 -0
  132. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_repo_hygiene_no_tracked_env_or_pyc.py +0 -0
  133. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_repo_no_deep_imports_in_user_facing_docs.py +0 -0
  134. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_bridge_fake_backend.py +0 -0
  135. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_concurrency.py +0 -0
  136. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_engine.py +0 -0
  137. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_hitl_host_protocol.py +0 -0
  138. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_hooks_and_schema_gate.py +0 -0
  139. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_initial_history_and_meta.py +0 -0
  140. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_manifest.py +0 -0
  141. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_result_prompt_hardening.py +0 -0
  142. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_service_facade_rpc_mapping.py +0 -0
  143. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_service_session_bridge.py +0 -0
  144. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_status_mapping.py +0 -0
  145. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_runtime_structured_output_bridge.py +0 -0
  146. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_sandbox_permissions_passthrough_contract.py +0 -0
  147. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_services_call_callback.py +0 -0
  148. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_services_map_node_status.py +0 -0
  149. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_skills_conformance_smoke.py +0 -0
  150. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_adapter_tool_registration.py +0 -0
  151. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_chat_backend_protocol_contract.py +0 -0
  152. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_chat_sse_usage_contract.py +0 -0
  153. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_compat_spaces_schema.py +0 -0
  154. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_prompt_profile_contract.py +0 -0
  155. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_runtime_client_server_contract.py +0 -0
  156. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_sandbox_profile_precedence_contract.py +0 -0
  157. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_skills_bundles_contract.py +0 -0
  158. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_tool_descriptor_compat.py +0 -0
  159. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_upstream_verification.py +0 -0
  160. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_versioning_strategy_guard.py +0 -0
  161. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_wal_backend_injection_contract.py +0 -0
  162. {capability_runtime-0.1.3.post1 → capability_runtime-0.1.5}/tests/test_wal_locator_resolution_contract.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capability-runtime
3
- Version: 0.1.3.post1
3
+ Version: 0.1.5
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: PyYAML>=6
9
9
  Requires-Dist: pydantic<3,>=2
10
- Requires-Dist: agently==4.0.8
10
+ Requires-Dist: agently==4.1.3.1
11
11
  Requires-Dist: skills-runtime-sdk==0.1.12
12
12
  Provides-Extra: dev
13
13
  Requires-Dist: pytest>=7; extra == "dev"
@@ -40,7 +40,10 @@ The public contract of this repository is intentionally narrow:
40
40
  - Public capability registration and manifest descriptors
41
41
  - Workflow orchestration on top of the runtime without exposing TriggerFlow as a public API
42
42
  - Evidence-first results through `NodeReport`, tool-call reports, approval summaries, and WAL locators
43
- - Host-facing helpers for wait/resume, approval tickets, continuity, and service streaming
43
+ - Host-facing helpers for waiting summaries, approval tickets, resume-intent preview, continuity, and service streaming
44
+ - Runtime capability previews behind the same runtime contract:
45
+ Responses requester opt-in, Dynamic DAG preview, workflow lifecycle summaries,
46
+ Workspace/Recall context packs, and Action artifact evidence summaries
44
47
 
45
48
  ## Architecture At A Glance
46
49
 
@@ -48,7 +51,7 @@ The public contract of this repository is intentionally narrow:
48
51
  +-----------------------------+
49
52
  | Host Application |
50
53
  | - register capabilities |
51
- | - run / stream / continue |
54
+ | - run / stream / summarize |
52
55
  +--------------+--------------+
53
56
  |
54
57
  v
@@ -131,7 +134,27 @@ python examples/01_quickstart/run_bridge.py
131
134
  ```
132
135
 
133
136
  Bridge mode reuses Agently's OpenAI-compatible transport but still delegates the
134
- actual skills/tools/WAL semantics to `skills-runtime-sdk`.
137
+ actual skills/tools/WAL semantics to `skills-runtime-sdk`. The default requester
138
+ strategy is `chat_completions`. Responses mode is an explicit opt-in with
139
+ `RuntimeConfig.requester_strategy="responses"`; it is not the default.
140
+
141
+ Real provider wiring order:
142
+
143
+ 1. Verify the provider model name with the gateway or `/models` surface.
144
+ 2. Build a provider requester factory from neutral transport settings with
145
+ `build_openai_provider_requester_factory(...)`.
146
+ 3. Keep chat/completions on the default `requester_strategy="chat_completions"`.
147
+ 4. Opt in to the responses lane only if the provider supports `/responses`.
148
+ 5. Run the runtime chat path with
149
+ `RuntimeConfig(mode="bridge", requester_strategy="chat_completions")`.
150
+ 6. Run the runtime responses path with
151
+ `RuntimeConfig(mode="bridge", requester_strategy="responses")`.
152
+
153
+ Model routing has a separate priority chain: `AgentSpec.llm_config["model"]`
154
+ overrides the SDK `ChatRequest.model`, and that request model is the runtime
155
+ bridge source of truth when the provider usage payload omits `model`. Agently
156
+ settings configure transport details such as base URL, auth, and requester
157
+ plugin settings; they do not replace `AgentSpec.llm_config.model`.
135
158
 
136
159
  ### 3. Workflow orchestration
137
160
 
@@ -139,6 +162,19 @@ actual skills/tools/WAL semantics to `skills-runtime-sdk`.
139
162
  python examples/02_workflow/run.py
140
163
  ```
141
164
 
165
+ ### 4. Runtime capability previews
166
+
167
+ ```bash
168
+ python examples/05_dynamic_dag_preview/run.py
169
+ python examples/06_responses_bridge/run.py
170
+ ```
171
+
172
+ These previews keep the public surface inside `capability_runtime`: use
173
+ `DynamicWorkflowPlan` / runtime methods for Dynamic DAGs and
174
+ `RuntimeConfig.requester_strategy` for Responses. Do not build downstream code on
175
+ upstream-native `TaskDAG`, `DynamicTask`, `Workspace`, `Action`, or requester
176
+ objects.
177
+
142
178
  For a higher-level index, start with [examples/README.md](examples/README.md).
143
179
 
144
180
  ## Public API At A Glance
@@ -150,6 +186,10 @@ from capability_runtime import (
150
186
  Runtime,
151
187
  RuntimeConfig,
152
188
  CustomTool,
189
+ ProviderRequester,
190
+ ProviderRequesterFactory,
191
+ ProviderRequesterStrategy,
192
+ build_openai_provider_requester_factory,
153
193
  AgentSpec,
154
194
  AgentIOSchema,
155
195
  WorkflowSpec,
@@ -162,10 +202,19 @@ from capability_runtime import (
162
202
  CapabilityKind,
163
203
  CapabilityResult,
164
204
  CapabilityStatus,
205
+ DynamicWorkflowNode,
206
+ DynamicWorkflowPlan,
165
207
  NodeReport,
208
+ StructuredStreamEvent,
166
209
  HostRunSnapshot,
210
+ HostRunStatus,
167
211
  ApprovalTicket,
168
212
  ResumeIntent,
213
+ RuntimeContextRecordRef,
214
+ RuntimeRecallBackend,
215
+ RuntimeRecallContextPack,
216
+ build_recall_context_pack,
217
+ write_node_report_summary,
169
218
  RuntimeServiceFacade,
170
219
  RuntimeServiceRequest,
171
220
  RuntimeServiceHandle,
@@ -176,8 +225,26 @@ from capability_runtime import (
176
225
  The runtime currently supports three execution modes through `RuntimeConfig.mode`:
177
226
 
178
227
  - `mock`: deterministic local testing without a real LLM backend
179
- - `bridge`: Agently transport + `skills-runtime-sdk` execution semantics
180
- - `sdk_native`: native `skills-runtime-sdk` backend without Agently transport
228
+ - `bridge`: provider transport adapter + `skills-runtime-sdk` execution semantics
229
+ - `sdk_native`: native `skills-runtime-sdk` backend without the provider transport adapter
230
+
231
+ Provider bridge strategy is separate from `mode`:
232
+
233
+ - `RuntimeConfig.requester_strategy="chat_completions"`: backward-compatible
234
+ default bridge path.
235
+ - `RuntimeConfig.requester_strategy="responses"`: opt-in Responses bridge path.
236
+ - `sdk_backend` injection takes precedence over both strategies for offline
237
+ regression and fake-backend tests.
238
+
239
+ Dynamic DAG preview is separate from `WorkflowSpec.steps`: compile a TaskDAG-like
240
+ mapping into the runtime-owned `DynamicWorkflowPlan`, keep `max_dynamic_nodes`
241
+ bounded, and execute nodes only through registered capabilities.
242
+
243
+ Provider audit evidence is expected in `NodeReport.usage`: preserve
244
+ `model`, `request_id`, `provider`, and token counts when the provider returns
245
+ them. If the provider omits `model`, the request model from `ChatRequest.model`
246
+ is the fallback; never treat an SDK placeholder such as `gpt-4` as real provider
247
+ evidence when a more specific request/provider model is available.
181
248
 
182
249
  ## Repository Layout
183
250
 
@@ -226,7 +293,15 @@ configure the corresponding Trusted Publisher entry on `pypi.org`.
226
293
 
227
294
  - `skills-runtime-sdk` remains the source of truth for skills, approvals, tools,
228
295
  WAL, and event evidence.
296
+ - Agently `SkillsExecutor` is not a `capability-runtime` skills driver. Its
297
+ authoring patterns may inform `SKILL.md` bundle design, but skill injection,
298
+ tool execution, approvals, WAL, events, and `NodeReport` aggregation remain
299
+ owned by `skills-runtime-sdk`.
229
300
  - `Agently` remains the transport/orchestration substrate where this repository
230
301
  chooses to bridge instead of forking or reimplementing.
231
302
  - `capability-runtime` is the contract-convergence layer: it narrows those
232
303
  upstream capabilities into a smaller host-facing runtime surface.
304
+ - Default bridge mode is non-breaking: if you do not configure Responses, Dynamic DAG,
305
+ Workspace/Recall, or Action artifact evidence previews, existing
306
+ `Runtime.run()` / `Runtime.run_stream()` / `WorkflowSpec` consumers can keep
307
+ reading the existing `NodeReport` and UI event fields.
@@ -24,7 +24,10 @@ The public contract of this repository is intentionally narrow:
24
24
  - Public capability registration and manifest descriptors
25
25
  - Workflow orchestration on top of the runtime without exposing TriggerFlow as a public API
26
26
  - Evidence-first results through `NodeReport`, tool-call reports, approval summaries, and WAL locators
27
- - Host-facing helpers for wait/resume, approval tickets, continuity, and service streaming
27
+ - Host-facing helpers for waiting summaries, approval tickets, resume-intent preview, continuity, and service streaming
28
+ - Runtime capability previews behind the same runtime contract:
29
+ Responses requester opt-in, Dynamic DAG preview, workflow lifecycle summaries,
30
+ Workspace/Recall context packs, and Action artifact evidence summaries
28
31
 
29
32
  ## Architecture At A Glance
30
33
 
@@ -32,7 +35,7 @@ The public contract of this repository is intentionally narrow:
32
35
  +-----------------------------+
33
36
  | Host Application |
34
37
  | - register capabilities |
35
- | - run / stream / continue |
38
+ | - run / stream / summarize |
36
39
  +--------------+--------------+
37
40
  |
38
41
  v
@@ -115,7 +118,27 @@ python examples/01_quickstart/run_bridge.py
115
118
  ```
116
119
 
117
120
  Bridge mode reuses Agently's OpenAI-compatible transport but still delegates the
118
- actual skills/tools/WAL semantics to `skills-runtime-sdk`.
121
+ actual skills/tools/WAL semantics to `skills-runtime-sdk`. The default requester
122
+ strategy is `chat_completions`. Responses mode is an explicit opt-in with
123
+ `RuntimeConfig.requester_strategy="responses"`; it is not the default.
124
+
125
+ Real provider wiring order:
126
+
127
+ 1. Verify the provider model name with the gateway or `/models` surface.
128
+ 2. Build a provider requester factory from neutral transport settings with
129
+ `build_openai_provider_requester_factory(...)`.
130
+ 3. Keep chat/completions on the default `requester_strategy="chat_completions"`.
131
+ 4. Opt in to the responses lane only if the provider supports `/responses`.
132
+ 5. Run the runtime chat path with
133
+ `RuntimeConfig(mode="bridge", requester_strategy="chat_completions")`.
134
+ 6. Run the runtime responses path with
135
+ `RuntimeConfig(mode="bridge", requester_strategy="responses")`.
136
+
137
+ Model routing has a separate priority chain: `AgentSpec.llm_config["model"]`
138
+ overrides the SDK `ChatRequest.model`, and that request model is the runtime
139
+ bridge source of truth when the provider usage payload omits `model`. Agently
140
+ settings configure transport details such as base URL, auth, and requester
141
+ plugin settings; they do not replace `AgentSpec.llm_config.model`.
119
142
 
120
143
  ### 3. Workflow orchestration
121
144
 
@@ -123,6 +146,19 @@ actual skills/tools/WAL semantics to `skills-runtime-sdk`.
123
146
  python examples/02_workflow/run.py
124
147
  ```
125
148
 
149
+ ### 4. Runtime capability previews
150
+
151
+ ```bash
152
+ python examples/05_dynamic_dag_preview/run.py
153
+ python examples/06_responses_bridge/run.py
154
+ ```
155
+
156
+ These previews keep the public surface inside `capability_runtime`: use
157
+ `DynamicWorkflowPlan` / runtime methods for Dynamic DAGs and
158
+ `RuntimeConfig.requester_strategy` for Responses. Do not build downstream code on
159
+ upstream-native `TaskDAG`, `DynamicTask`, `Workspace`, `Action`, or requester
160
+ objects.
161
+
126
162
  For a higher-level index, start with [examples/README.md](examples/README.md).
127
163
 
128
164
  ## Public API At A Glance
@@ -134,6 +170,10 @@ from capability_runtime import (
134
170
  Runtime,
135
171
  RuntimeConfig,
136
172
  CustomTool,
173
+ ProviderRequester,
174
+ ProviderRequesterFactory,
175
+ ProviderRequesterStrategy,
176
+ build_openai_provider_requester_factory,
137
177
  AgentSpec,
138
178
  AgentIOSchema,
139
179
  WorkflowSpec,
@@ -146,10 +186,19 @@ from capability_runtime import (
146
186
  CapabilityKind,
147
187
  CapabilityResult,
148
188
  CapabilityStatus,
189
+ DynamicWorkflowNode,
190
+ DynamicWorkflowPlan,
149
191
  NodeReport,
192
+ StructuredStreamEvent,
150
193
  HostRunSnapshot,
194
+ HostRunStatus,
151
195
  ApprovalTicket,
152
196
  ResumeIntent,
197
+ RuntimeContextRecordRef,
198
+ RuntimeRecallBackend,
199
+ RuntimeRecallContextPack,
200
+ build_recall_context_pack,
201
+ write_node_report_summary,
153
202
  RuntimeServiceFacade,
154
203
  RuntimeServiceRequest,
155
204
  RuntimeServiceHandle,
@@ -160,8 +209,26 @@ from capability_runtime import (
160
209
  The runtime currently supports three execution modes through `RuntimeConfig.mode`:
161
210
 
162
211
  - `mock`: deterministic local testing without a real LLM backend
163
- - `bridge`: Agently transport + `skills-runtime-sdk` execution semantics
164
- - `sdk_native`: native `skills-runtime-sdk` backend without Agently transport
212
+ - `bridge`: provider transport adapter + `skills-runtime-sdk` execution semantics
213
+ - `sdk_native`: native `skills-runtime-sdk` backend without the provider transport adapter
214
+
215
+ Provider bridge strategy is separate from `mode`:
216
+
217
+ - `RuntimeConfig.requester_strategy="chat_completions"`: backward-compatible
218
+ default bridge path.
219
+ - `RuntimeConfig.requester_strategy="responses"`: opt-in Responses bridge path.
220
+ - `sdk_backend` injection takes precedence over both strategies for offline
221
+ regression and fake-backend tests.
222
+
223
+ Dynamic DAG preview is separate from `WorkflowSpec.steps`: compile a TaskDAG-like
224
+ mapping into the runtime-owned `DynamicWorkflowPlan`, keep `max_dynamic_nodes`
225
+ bounded, and execute nodes only through registered capabilities.
226
+
227
+ Provider audit evidence is expected in `NodeReport.usage`: preserve
228
+ `model`, `request_id`, `provider`, and token counts when the provider returns
229
+ them. If the provider omits `model`, the request model from `ChatRequest.model`
230
+ is the fallback; never treat an SDK placeholder such as `gpt-4` as real provider
231
+ evidence when a more specific request/provider model is available.
165
232
 
166
233
  ## Repository Layout
167
234
 
@@ -210,7 +277,15 @@ configure the corresponding Trusted Publisher entry on `pypi.org`.
210
277
 
211
278
  - `skills-runtime-sdk` remains the source of truth for skills, approvals, tools,
212
279
  WAL, and event evidence.
280
+ - Agently `SkillsExecutor` is not a `capability-runtime` skills driver. Its
281
+ authoring patterns may inform `SKILL.md` bundle design, but skill injection,
282
+ tool execution, approvals, WAL, events, and `NodeReport` aggregation remain
283
+ owned by `skills-runtime-sdk`.
213
284
  - `Agently` remains the transport/orchestration substrate where this repository
214
285
  chooses to bridge instead of forking or reimplementing.
215
286
  - `capability-runtime` is the contract-convergence layer: it narrows those
216
287
  upstream capabilities into a smaller host-facing runtime surface.
288
+ - Default bridge mode is non-breaking: if you do not configure Responses, Dynamic DAG,
289
+ Workspace/Recall, or Action artifact evidence previews, existing
290
+ `Runtime.run()` / `Runtime.run_stream()` / `WorkflowSpec` consumers can keep
291
+ reading the existing `NodeReport` and UI event fields.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capability-runtime"
3
- version = "0.1.3.post1"
3
+ version = "0.1.5"
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"
@@ -8,7 +8,7 @@ license = "MIT"
8
8
  dependencies = [
9
9
  "PyYAML>=6",
10
10
  "pydantic>=2,<3",
11
- "agently==4.0.8",
11
+ "agently==4.1.3.1",
12
12
  "skills-runtime-sdk==0.1.12",
13
13
  ]
14
14
 
@@ -1,13 +1,29 @@
1
1
  """capability-runtime:统一 Runtime 入口(能力协议 + 执行 + 报告)。"""
2
2
  from __future__ import annotations
3
3
 
4
- __version__ = "0.1.3.post1"
4
+ __version__ = "0.1.5"
5
5
 
6
6
  # === 统一入口 ===
7
- from .config import CustomTool, RuntimeConfig
7
+ from .config import (
8
+ AgentlyRequesterStrategy,
9
+ CustomTool,
10
+ ProviderRequester,
11
+ ProviderRequesterFactory,
12
+ ProviderRequesterStrategy,
13
+ RuntimeConfig,
14
+ ToolChoiceAfterToolResult,
15
+ )
16
+ from .context_pack import (
17
+ RuntimeContextRecordRef,
18
+ RuntimeRecallBackend,
19
+ RuntimeRecallContextPack,
20
+ build_recall_context_pack,
21
+ write_node_report_summary,
22
+ )
8
23
  from .runtime import Runtime
9
24
  from .service_facade import RuntimeServiceFacade, RuntimeServiceHandle, RuntimeServiceRequest, RuntimeSession
10
25
  from .structured_stream import StructuredStreamEvent
26
+ from .adapters.agently_backend import build_openai_provider_requester_factory
11
27
 
12
28
  # === 报告类型 ===
13
29
  from .types import NodeReport, NodeResult
@@ -27,6 +43,7 @@ from .protocol.capability import (
27
43
  CapabilityStatus,
28
44
  )
29
45
  from .protocol.context import ExecutionContext
46
+ from .protocol.dynamic_workflow import DynamicWorkflowNode, DynamicWorkflowPlan
30
47
  from .protocol.workflow import (
31
48
  ConditionalStep,
32
49
  InputMapping,
@@ -35,7 +52,6 @@ from .protocol.workflow import (
35
52
  Step,
36
53
  WorkflowSpec,
37
54
  )
38
- from .services import RuntimeServices
39
55
  from .workflow_runtime import WorkflowReplayRequest, WorkflowRunSnapshot, WorkflowRunStatus, WorkflowStepSnapshot
40
56
 
41
57
  # === 错误导出 ===
@@ -45,8 +61,19 @@ __all__ = [
45
61
  # Runtime
46
62
  "Runtime",
47
63
  "RuntimeConfig",
64
+ "ProviderRequesterStrategy",
65
+ "AgentlyRequesterStrategy",
66
+ "ToolChoiceAfterToolResult",
67
+ "ProviderRequester",
68
+ "ProviderRequesterFactory",
48
69
  "CustomTool",
49
70
  "StructuredStreamEvent",
71
+ "RuntimeContextRecordRef",
72
+ "RuntimeRecallBackend",
73
+ "RuntimeRecallContextPack",
74
+ "build_recall_context_pack",
75
+ "write_node_report_summary",
76
+ "build_openai_provider_requester_factory",
50
77
  # Reports
51
78
  "NodeReport",
52
79
  "NodeResult",
@@ -77,6 +104,8 @@ __all__ = [
77
104
  "AgentSpec",
78
105
  "AgentIOSchema",
79
106
  "PromptRenderMode",
107
+ "DynamicWorkflowNode",
108
+ "DynamicWorkflowPlan",
80
109
  "WorkflowSpec",
81
110
  "Step",
82
111
  "LoopStep",
@@ -84,8 +113,11 @@ __all__ = [
84
113
  "ConditionalStep",
85
114
  "InputMapping",
86
115
  "ExecutionContext",
87
- "RuntimeServices",
88
116
  # Errors
89
117
  "RuntimeFrameworkError",
90
118
  "CapabilityNotFoundError",
91
119
  ]
120
+
121
+
122
+ def __getattr__(name: str):
123
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -20,12 +20,14 @@ from typing import Any, AsyncIterator, Dict, List, Optional, Union
20
20
  from skills_runtime.core.contracts import AgentEvent
21
21
  from skills_runtime.core.errors import FrameworkIssue
22
22
 
23
+ from ..errors import ProviderStreamTerminalError
23
24
  from ..logging_utils import log_suppressed_exception
24
25
  from ..protocol.agent import AgentSpec
25
26
  from ..protocol.capability import CapabilityResult, CapabilityStatus
26
27
  from ..protocol.context import ExecutionContext
27
- from ..reporting.node_report import NodeReportBuilder
28
+ from ..reporting.node_report import NodeReportBuilder, attach_agently_bridge_diagnostics
28
29
  from ..services import RuntimeServices, map_node_status
30
+ from ..types import NodeUsageReport
29
31
 
30
32
  # _build_task 使用的 prompt section 常量
31
33
  _SECTION_SYSTEM = "## 系统指令"
@@ -37,6 +39,7 @@ _RUNTIME_PROMPT_KEY = "_runtime_prompt"
37
39
  _PROMPT_HASH_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
38
40
  _ALLOWED_PROMPT_ROLES = {"system", "developer", "user", "assistant", "tool"}
39
41
  _ALLOWED_PROMPT_PROFILES = {"default_agent", "generation_direct", "structured_transform"}
42
+ _ALLOWED_IMAGE_DETAILS = {"auto", "low", "high"}
40
43
 
41
44
 
42
45
  class _InvalidPromptMessages(ValueError):
@@ -366,6 +369,67 @@ class AgentAdapter:
366
369
  node_report=report,
367
370
  )
368
371
  return
372
+ except ProviderStreamTerminalError as exc:
373
+ provider_terminal = exc.to_control_payload()
374
+ if any(ev.type == "provider_stream_terminal" for ev in events):
375
+ events.append(
376
+ AgentEvent(
377
+ type="run_failed",
378
+ timestamp="",
379
+ run_id=context.run_id,
380
+ turn_id=None,
381
+ payload={"error_kind": exc.reason, "message": exc.message},
382
+ )
383
+ )
384
+ report = NodeReportBuilder().build(events=events)
385
+ attach_agently_bridge_diagnostics(
386
+ report,
387
+ mode=getattr(self._services.config, "mode", ""),
388
+ requester_strategy=getattr(self._services.config, "effective_requester_strategy", "chat_completions"),
389
+ )
390
+ report.meta["capability_id"] = spec.base.id
391
+ report.meta["source"] = "provider_stream"
392
+ else:
393
+ report = self._services.build_fail_closed_report(
394
+ run_id=context.run_id,
395
+ status=exc.status,
396
+ reason=exc.reason,
397
+ completion_reason=exc.completion_reason,
398
+ meta={
399
+ "capability_id": spec.base.id,
400
+ "source": "provider_stream",
401
+ "provider_terminal": provider_terminal,
402
+ **prompt_plan.evidence,
403
+ },
404
+ )
405
+ if (
406
+ exc.model is not None
407
+ or exc.request_id is not None
408
+ or exc.provider is not None
409
+ or exc.provider_transport is not None
410
+ ):
411
+ report.usage = NodeUsageReport(
412
+ model=exc.model,
413
+ request_id=exc.request_id,
414
+ provider=exc.provider,
415
+ provider_transport=exc.provider_transport,
416
+ )
417
+ self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
418
+ _apply_on_event_error_evidence(report)
419
+ status = map_node_status(report)
420
+ error_code = (
421
+ str(provider_terminal.get("error_code"))
422
+ if isinstance(provider_terminal.get("error_code"), str)
423
+ else None
424
+ )
425
+ yield CapabilityResult(
426
+ status=status,
427
+ error=exc.message if status == CapabilityStatus.FAILED else None,
428
+ error_code=error_code,
429
+ report=report,
430
+ node_report=report,
431
+ )
432
+ return
369
433
  except Exception as exc:
370
434
  report = self._services.build_fail_closed_report(
371
435
  run_id=context.run_id,
@@ -385,6 +449,11 @@ class AgentAdapter:
385
449
  return
386
450
 
387
451
  report = NodeReportBuilder().build(events=events)
452
+ attach_agently_bridge_diagnostics(
453
+ report,
454
+ mode=getattr(self._services.config, "mode", ""),
455
+ requester_strategy=getattr(self._services.config, "effective_requester_strategy", "chat_completions"),
456
+ )
388
457
  self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
389
458
  _apply_on_event_error_evidence(report)
390
459
  if issues and getattr(self._services.config, "preflight_mode", "error") == "warn":
@@ -417,10 +486,20 @@ class AgentAdapter:
417
486
  )
418
487
 
419
488
  status = map_node_status(report)
489
+ provider_terminal = report.meta.get("provider_terminal") if isinstance(report.meta, dict) else None
490
+ terminal_error_code = None
491
+ if isinstance(provider_terminal, dict) and isinstance(provider_terminal.get("error_code"), str):
492
+ terminal_error_code = provider_terminal["error_code"]
493
+ terminal_error = None
494
+ if isinstance(provider_terminal, dict) and isinstance(provider_terminal.get("message"), str):
495
+ terminal_error = provider_terminal["message"]
496
+ if final_output.startswith("CAPRT_PROVIDER_STREAM_TERMINAL "):
497
+ final_output = terminal_error if status == CapabilityStatus.FAILED else ""
420
498
  yield CapabilityResult(
421
499
  status=status,
422
500
  output=final_output,
423
- error=report.reason if status == CapabilityStatus.FAILED else None,
501
+ error=terminal_error if terminal_error is not None and status == CapabilityStatus.FAILED else (report.reason if status == CapabilityStatus.FAILED else None),
502
+ error_code=terminal_error_code,
424
503
  report=report,
425
504
  node_report=report,
426
505
  artifacts=list(report.artifacts),
@@ -578,6 +657,10 @@ class AgentAdapter:
578
657
  if messages is not None:
579
658
  evidence["prompt_messages_count"] = len(messages)
580
659
  evidence["prompt_message_roles"] = [str(item["role"]) for item in messages]
660
+ modalities, content_part_counts, media_count = _summarize_precomposed_message_content(messages)
661
+ evidence["prompt_modalities"] = modalities
662
+ evidence["prompt_content_part_counts"] = content_part_counts
663
+ evidence["prompt_media_count"] = media_count
581
664
  return evidence
582
665
 
583
666
  def _strip_runtime_prompt(self, input: Dict[str, Any]) -> Dict[str, Any]:
@@ -736,18 +819,27 @@ def _hash_text(text: str) -> str:
736
819
  def _hash_messages(messages: List[Dict[str, Any]]) -> str:
737
820
  """对 provider messages 做稳定 JSON canonicalization 后生成摘要。"""
738
821
 
739
- canonical = json.dumps(messages, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
822
+ canonical = _canonicalize_messages(messages)
740
823
  return _hash_text(canonical)
741
824
 
742
825
 
826
+ def _canonicalize_messages(messages: List[Dict[str, Any]]) -> str:
827
+ """对 provider messages 做稳定 JSON canonicalization。"""
828
+
829
+ try:
830
+ return json.dumps(messages, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
831
+ except (TypeError, ValueError) as exc:
832
+ raise _InvalidPromptMessages("_runtime_prompt.messages must be JSON canonicalizable") from exc
833
+
834
+
743
835
  def _validate_precomposed_messages(raw: Any) -> List[Dict[str, Any]]:
744
836
  """
745
837
  校验 host 提供的最终 provider messages。
746
838
 
747
839
  约束:
748
840
  - 只接受非空 list[dict];
749
- - 每条消息必须有合法 role 与字符串 content;
750
- - 返回浅拷贝,避免后续执行链路修改调用方输入。
841
+ - 每条消息必须有合法 role 与字符串或稳定 content parts
842
+ - 返回深拷贝,避免后续执行链路修改调用方输入。
751
843
  """
752
844
 
753
845
  if not isinstance(raw, list):
@@ -763,10 +855,92 @@ def _validate_precomposed_messages(raw: Any) -> List[Dict[str, Any]]:
763
855
  content = item.get("content")
764
856
  if not isinstance(role, str) or role not in _ALLOWED_PROMPT_ROLES:
765
857
  raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].role is invalid")
766
- if not isinstance(content, str):
767
- raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].content must be a string")
768
858
  copied = dict(item)
769
859
  copied["role"] = role
770
- copied["content"] = content
860
+ copied["content"] = _validate_precomposed_message_content(content, message_index=idx)
771
861
  out.append(copied)
862
+ return json.loads(_canonicalize_messages(out))
863
+
864
+
865
+ def _validate_precomposed_message_content(content: Any, *, message_index: int) -> Union[str, List[Dict[str, Any]]]:
866
+ """校验单条 precomposed message 的 content 字段。"""
867
+
868
+ if isinstance(content, str):
869
+ return content
870
+ if not isinstance(content, list):
871
+ raise _InvalidPromptMessages(
872
+ f"_runtime_prompt.messages[{message_index}].content must be a string or content part list"
873
+ )
874
+ if not content:
875
+ raise _InvalidPromptMessages(f"_runtime_prompt.messages[{message_index}].content must not be an empty list")
876
+
877
+ out: List[Dict[str, Any]] = []
878
+ for part_index, part in enumerate(content):
879
+ out.append(_validate_precomposed_content_part(part, message_index=message_index, part_index=part_index))
772
880
  return out
881
+
882
+
883
+ def _validate_precomposed_content_part(part: Any, *, message_index: int, part_index: int) -> Dict[str, Any]:
884
+ """校验 v1 稳定支持的 OpenAI-compatible content part。"""
885
+
886
+ path = f"_runtime_prompt.messages[{message_index}].content[{part_index}]"
887
+ if not isinstance(part, dict):
888
+ raise _InvalidPromptMessages(f"{path} must be a dict")
889
+ part_type = part.get("type")
890
+ if not isinstance(part_type, str) or not part_type:
891
+ raise _InvalidPromptMessages(f"{path}.type is required")
892
+ if part_type == "text":
893
+ if set(part.keys()) != {"type", "text"}:
894
+ raise _InvalidPromptMessages(f"{path} has unsupported text part fields")
895
+ text = part.get("text")
896
+ if not isinstance(text, str):
897
+ raise _InvalidPromptMessages(f"{path}.text must be a string")
898
+ return {"type": "text", "text": text}
899
+ if part_type == "image_url":
900
+ if set(part.keys()) != {"type", "image_url"}:
901
+ raise _InvalidPromptMessages(f"{path} has unsupported image_url part fields")
902
+ image_url = part.get("image_url")
903
+ if not isinstance(image_url, dict):
904
+ raise _InvalidPromptMessages(f"{path}.image_url must be a dict")
905
+ allowed_image_keys = {"url", "detail"}
906
+ if not set(image_url.keys()).issubset(allowed_image_keys):
907
+ raise _InvalidPromptMessages(f"{path}.image_url has unsupported fields")
908
+ url = image_url.get("url")
909
+ if not isinstance(url, str) or not url.strip():
910
+ raise _InvalidPromptMessages(f"{path}.image_url.url must be a non-empty string")
911
+ copied_image_url: Dict[str, Any] = {"url": url}
912
+ if "detail" in image_url:
913
+ detail = image_url.get("detail")
914
+ if detail not in _ALLOWED_IMAGE_DETAILS:
915
+ raise _InvalidPromptMessages(f"{path}.image_url.detail is invalid")
916
+ copied_image_url["detail"] = detail
917
+ return {"type": "image_url", "image_url": copied_image_url}
918
+ raise _InvalidPromptMessages(f"{path}.type is unsupported")
919
+
920
+
921
+ def _summarize_precomposed_message_content(
922
+ messages: List[Dict[str, Any]],
923
+ ) -> tuple[List[str], List[int], int]:
924
+ """生成 precomposed messages 的最小披露多模态摘要。"""
925
+
926
+ modalities: set[str] = set()
927
+ content_part_counts: List[int] = []
928
+ media_count = 0
929
+ for message in messages:
930
+ content = message.get("content")
931
+ if isinstance(content, str):
932
+ modalities.add("text")
933
+ content_part_counts.append(0)
934
+ continue
935
+ if isinstance(content, list):
936
+ content_part_counts.append(len(content))
937
+ for part in content:
938
+ part_type = part.get("type") if isinstance(part, dict) else None
939
+ if part_type == "text":
940
+ modalities.add("text")
941
+ elif part_type == "image_url":
942
+ modalities.add("image")
943
+ media_count += 1
944
+ continue
945
+ content_part_counts.append(0)
946
+ return sorted(modalities), content_part_counts, media_count