capability-runtime 0.1.2__tar.gz → 0.1.3.post1__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.
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/PKG-INFO +1 -1
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/pyproject.toml +1 -1
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/__init__.py +1 -1
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/adapters/agent_adapter.py +54 -10
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/adapters/triggerflow_workflow_engine.py +2 -3
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_protocol.py +9 -1
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/runtime.py +46 -9
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/sdk_lifecycle.py +87 -3
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/structured_output.py +42 -16
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime.egg-info/PKG-INFO +1 -1
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime.egg-info/SOURCES.txt +1 -0
- capability_runtime-0.1.3.post1/tests/test_agently_backend.py +630 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_release_tag_version_guardrail.py +2 -2
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_hitl_host_protocol.py +41 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_hooks_and_schema_gate.py +60 -0
- capability_runtime-0.1.3.post1/tests/test_runtime_result_prompt_hardening.py +284 -0
- capability_runtime-0.1.2/tests/test_agently_backend.py +0 -285
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/README.md +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/setup.cfg +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/adapters/__init__.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/adapters/agently_backend.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/adapters/workflow_engine.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/config.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/errors.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/guards.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/__init__.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/approvals_profiles.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/evidence_hooks.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/history.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/invoke_capability.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/resume.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/system_prompt.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_toolkit/turn_delta.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/logging_utils.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/manifest.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/output_validator.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/protocol/__init__.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/protocol/agent.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/protocol/capability.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/protocol/chat_backend.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/protocol/context.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/protocol/workflow.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/registry.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/reporting/__init__.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/reporting/node_report.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/runtime_ui_events_mixin.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/service_facade.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/services.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/structured_stream.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/types.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/ui_events/__init__.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/ui_events/projector.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/ui_events/session.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/ui_events/store.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/ui_events/transport.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/ui_events/v1.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/upstream_compat.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/utils/__init__.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/utils/usage.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/workflow_runtime.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime.egg-info/dependency_links.txt +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime.egg-info/requires.txt +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime.egg-info/top_level.txt +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_agently_backend_replay.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_backend_mode.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_bilingual_docs_surface.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_bridge_artifacts_passthrough.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_bridge_register_tool_public_api.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_coding_agent_examples_atomic.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_coding_agent_examples_recipes.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_config_glue.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_dependency_pins.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_docs_pinned_dependency_versions.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_docs_scheme2_no_skilladapter_residue.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_error_observability.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_alignment_fixes_l1.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_no_agent_sdk_imports.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_real_evidence_strict_integration.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_real_integration.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_smoke.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_ui_events_showcase_offline.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_ui_events_showcase_real_fallback.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_ui_events_showcase_real_integration.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_ui_events_showcase_ui_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_examples_workflow_skills_first_smoke.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_guards.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_approvals_profiles.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_history_assembler.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_invoke_capability.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_invoke_capability_shared_runtime.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_resume_helper.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_system_prompt_evidence.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_host_toolkit_turn_delta.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_integration_agently_requester_smoke.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_integration_approval_event_shape.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_integration_sources_redis_pgsql_smoke.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_loop.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_node_report_builder.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_node_report_contract_v1.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_node_report_engine_identity_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_offline_backend_injection_evidence.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_per_capability_llm_config_model_routing.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_preflight_gate.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_project_identity_naming_matrix.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_public_api_exports.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_public_repo_hygiene.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_qa_agent.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_registry.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_replay_tool_calls_alignment.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_repo_hygiene_no_tracked_env_or_pyc.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_repo_no_deep_imports_in_user_facing_docs.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_review_followups_module_contracts.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_bridge_fake_backend.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_concurrency.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_engine.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_initial_history_and_meta.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_manifest.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_run_stream_semantics_v1.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_service_facade_rpc_mapping.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_service_session_bridge.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_status_mapping.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_structured_output_bridge.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_runtime_structured_stream.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_sandbox_permissions_passthrough_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_services_call_callback.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_services_map_node_status.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_skills_conformance_smoke.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_adapter_tool_registration.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_chat_backend_protocol_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_chat_sse_usage_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_compat_spaces_schema.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_prompt_profile_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_runtime_client_server_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_sandbox_profile_precedence_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_skills_bundles_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_tool_descriptor_compat.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_upstream_verification.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_versioning_strategy_guard.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_wal_backend_injection_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_wal_locator_resolution_contract.py +0 -0
- {capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/tests/test_workflow_host_runtime_surface.py +0 -0
|
@@ -80,6 +80,7 @@ class AgentAdapter:
|
|
|
80
80
|
spec: AgentSpec,
|
|
81
81
|
input: Dict[str, Any],
|
|
82
82
|
context: ExecutionContext,
|
|
83
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
83
84
|
) -> AsyncIterator[Union[AgentEvent, CapabilityResult]]:
|
|
84
85
|
"""
|
|
85
86
|
流式执行 AgentSpec:先 yield AgentEvent(若为真实执行),最后 yield CapabilityResult。
|
|
@@ -93,6 +94,7 @@ class AgentAdapter:
|
|
|
93
94
|
|
|
94
95
|
备注:
|
|
95
96
|
- 每条 `AgentEvent` 也会通过 Runtime 的内部 tap 旁路分发(用于 UI events 投影;不改变对外 `AgentEvent` 转发语义)。
|
|
97
|
+
- `prompt_control` 为显式 prompt 控制面,优先于兼容入口 `input["_runtime_prompt"]`。
|
|
96
98
|
"""
|
|
97
99
|
|
|
98
100
|
mode = str(getattr(self._services.config, "mode", "mock"))
|
|
@@ -100,7 +102,12 @@ class AgentAdapter:
|
|
|
100
102
|
yield await self._mock_execute(spec=spec, input=input, context=context)
|
|
101
103
|
return
|
|
102
104
|
|
|
103
|
-
async for item in self._bridge_execute_stream(
|
|
105
|
+
async for item in self._bridge_execute_stream(
|
|
106
|
+
spec=spec,
|
|
107
|
+
input=input,
|
|
108
|
+
context=context,
|
|
109
|
+
prompt_control=prompt_control,
|
|
110
|
+
):
|
|
104
111
|
yield item
|
|
105
112
|
|
|
106
113
|
async def _mock_execute(self, *, spec: AgentSpec, input: Dict[str, Any], context: ExecutionContext) -> CapabilityResult:
|
|
@@ -193,14 +200,26 @@ class AgentAdapter:
|
|
|
193
200
|
)
|
|
194
201
|
|
|
195
202
|
async def _bridge_execute_stream(
|
|
196
|
-
self,
|
|
203
|
+
self,
|
|
204
|
+
*,
|
|
205
|
+
spec: AgentSpec,
|
|
206
|
+
input: Dict[str, Any],
|
|
207
|
+
context: ExecutionContext,
|
|
208
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
197
209
|
) -> AsyncIterator[Union[AgentEvent, CapabilityResult]]:
|
|
198
210
|
"""
|
|
199
211
|
真实执行(bridge/sdk_native):驱动 SDK Agent.run_stream_async 并聚合 NodeReport。
|
|
212
|
+
|
|
213
|
+
参数:
|
|
214
|
+
- prompt_control:显式 prompt 控制面;为空时回退读取 `input["_runtime_prompt"]`。
|
|
200
215
|
"""
|
|
201
216
|
|
|
202
217
|
try:
|
|
203
|
-
prompt_plan = self._resolve_prompt_render_plan(
|
|
218
|
+
prompt_plan = self._resolve_prompt_render_plan(
|
|
219
|
+
spec=spec,
|
|
220
|
+
input=input,
|
|
221
|
+
prompt_control=prompt_control,
|
|
222
|
+
)
|
|
204
223
|
except _InvalidPromptMessages as exc:
|
|
205
224
|
report = self._services.build_fail_closed_report(
|
|
206
225
|
run_id=context.run_id,
|
|
@@ -283,6 +302,19 @@ class AgentAdapter:
|
|
|
283
302
|
events: List[AgentEvent] = []
|
|
284
303
|
host_meta = self._services.get_host_meta(context=context)
|
|
285
304
|
initial_history = host_meta.get("initial_history") if isinstance(host_meta.get("initial_history"), list) else None
|
|
305
|
+
on_event_error_count = 0
|
|
306
|
+
on_event_last_error_type: Optional[str] = None
|
|
307
|
+
|
|
308
|
+
def _apply_on_event_error_evidence(report: Any) -> None:
|
|
309
|
+
"""把 on_event fail-open 错误摘要写入 NodeReport.meta。"""
|
|
310
|
+
|
|
311
|
+
if on_event_error_count <= 0:
|
|
312
|
+
return
|
|
313
|
+
meta = getattr(report, "meta", None)
|
|
314
|
+
if isinstance(meta, dict):
|
|
315
|
+
meta["on_event_error_count"] = on_event_error_count
|
|
316
|
+
if on_event_last_error_type:
|
|
317
|
+
meta["on_event_last_error_type"] = on_event_last_error_type
|
|
286
318
|
|
|
287
319
|
try:
|
|
288
320
|
async for ev in agent.run_stream_async(task, run_id=context.run_id, initial_history=initial_history):
|
|
@@ -306,6 +338,8 @@ class AgentAdapter:
|
|
|
306
338
|
{"run_id": context.run_id, "capability_id": spec.base.id},
|
|
307
339
|
)
|
|
308
340
|
except Exception as cb_exc:
|
|
341
|
+
on_event_error_count += 1
|
|
342
|
+
on_event_last_error_type = type(cb_exc).__name__
|
|
309
343
|
log_suppressed_exception(
|
|
310
344
|
context="on_event_callback",
|
|
311
345
|
exc=cb_exc,
|
|
@@ -323,6 +357,7 @@ class AgentAdapter:
|
|
|
323
357
|
meta={"capability_id": spec.base.id, "source": "sdk_agent"},
|
|
324
358
|
)
|
|
325
359
|
self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
|
|
360
|
+
_apply_on_event_error_evidence(report)
|
|
326
361
|
yield CapabilityResult(
|
|
327
362
|
status=CapabilityStatus.CANCELLED,
|
|
328
363
|
error="execution cancelled",
|
|
@@ -339,6 +374,7 @@ class AgentAdapter:
|
|
|
339
374
|
completion_reason="engine_exception",
|
|
340
375
|
meta={"engine_exception": type(exc).__name__, **prompt_plan.evidence},
|
|
341
376
|
)
|
|
377
|
+
_apply_on_event_error_evidence(report)
|
|
342
378
|
yield CapabilityResult(
|
|
343
379
|
status=CapabilityStatus.FAILED,
|
|
344
380
|
error=str(exc),
|
|
@@ -350,6 +386,7 @@ class AgentAdapter:
|
|
|
350
386
|
|
|
351
387
|
report = NodeReportBuilder().build(events=events)
|
|
352
388
|
self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
|
|
389
|
+
_apply_on_event_error_evidence(report)
|
|
353
390
|
if issues and getattr(self._services.config, "preflight_mode", "error") == "warn":
|
|
354
391
|
report.meta["preflight_mode"] = "warn"
|
|
355
392
|
report.meta["preflight_issues"] = [self._services.redact_issue(i) for i in issues]
|
|
@@ -389,13 +426,20 @@ class AgentAdapter:
|
|
|
389
426
|
artifacts=list(report.artifacts),
|
|
390
427
|
)
|
|
391
428
|
|
|
392
|
-
def _resolve_prompt_render_plan(
|
|
429
|
+
def _resolve_prompt_render_plan(
|
|
430
|
+
self,
|
|
431
|
+
*,
|
|
432
|
+
spec: AgentSpec,
|
|
433
|
+
input: Dict[str, Any],
|
|
434
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
435
|
+
) -> _PromptRenderPlan:
|
|
393
436
|
"""
|
|
394
437
|
解析 AgentSpec + run-level `_runtime_prompt`,生成 prompt 渲染计划。
|
|
395
438
|
|
|
396
439
|
参数:
|
|
397
440
|
- spec:Agent 声明
|
|
398
441
|
- input:run 输入,其中 `_runtime_prompt` 为保留控制面
|
|
442
|
+
- prompt_control:显式控制面;若提供则优先于 input 内兼容保留键
|
|
399
443
|
|
|
400
444
|
返回:
|
|
401
445
|
- `_PromptRenderPlan`
|
|
@@ -404,13 +448,14 @@ class AgentAdapter:
|
|
|
404
448
|
- `_InvalidPromptMessages`:控制面非法,调用方应 fail-fast。
|
|
405
449
|
"""
|
|
406
450
|
|
|
407
|
-
envelope_raw = input.get(_RUNTIME_PROMPT_KEY)
|
|
451
|
+
envelope_raw = prompt_control if prompt_control is not None else input.get(_RUNTIME_PROMPT_KEY)
|
|
452
|
+
envelope_name = "prompt_control" if prompt_control is not None else "_runtime_prompt"
|
|
408
453
|
if envelope_raw is None:
|
|
409
454
|
envelope: Dict[str, Any] = {}
|
|
410
455
|
elif isinstance(envelope_raw, dict):
|
|
411
456
|
envelope = dict(envelope_raw)
|
|
412
457
|
else:
|
|
413
|
-
raise _InvalidPromptMessages("
|
|
458
|
+
raise _InvalidPromptMessages(f"{envelope_name} must be a dict")
|
|
414
459
|
|
|
415
460
|
raw_mode = envelope.get("mode") if "mode" in envelope else getattr(spec, "prompt_render_mode", "structured_task")
|
|
416
461
|
if raw_mode is None:
|
|
@@ -570,10 +615,9 @@ class AgentAdapter:
|
|
|
570
615
|
if business_input:
|
|
571
616
|
lines: List[str] = []
|
|
572
617
|
for k, v in business_input.items():
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
lines.append(f"- {k}: {json.dumps(v, ensure_ascii=False)}")
|
|
618
|
+
key_text = json.dumps(str(k), ensure_ascii=False)
|
|
619
|
+
value_text = json.dumps(v, ensure_ascii=False)
|
|
620
|
+
lines.append(f"- {key_text}: {value_text}")
|
|
577
621
|
parts.append(f"{_SECTION_INPUT}\n" + "\n".join(lines))
|
|
578
622
|
|
|
579
623
|
if spec.output_schema and spec.output_schema.fields:
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import uuid
|
|
6
6
|
from contextlib import suppress
|
|
7
|
-
from dataclasses import dataclass
|
|
7
|
+
from dataclasses import dataclass, replace
|
|
8
8
|
from types import MappingProxyType
|
|
9
9
|
from typing import Any, AsyncIterator, Dict, List, Optional, cast
|
|
10
10
|
|
|
@@ -598,8 +598,7 @@ class TriggerFlowWorkflowEngine:
|
|
|
598
598
|
completion_reason="loop_iteration_failed",
|
|
599
599
|
meta={"step_id": step.id, "capability_id": step.capability.id},
|
|
600
600
|
)
|
|
601
|
-
result
|
|
602
|
-
result.node_report = report
|
|
601
|
+
result = replace(result, report=report, node_report=report)
|
|
603
602
|
|
|
604
603
|
context.step_outputs[step.id] = result.output
|
|
605
604
|
context.step_results[step.id] = _to_step_result_dict(result)
|
{capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/host_protocol.py
RENAMED
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
"""HITL / wait-resume / approval 的宿主协议收敛层。"""
|
|
4
4
|
|
|
5
|
+
import html
|
|
6
|
+
import re
|
|
5
7
|
from dataclasses import dataclass, field
|
|
6
8
|
from enum import Enum
|
|
7
9
|
from typing import Any, Optional
|
|
@@ -397,4 +399,10 @@ def _build_message_preview(value: Any) -> str | None:
|
|
|
397
399
|
normalized = " ".join(value.split())
|
|
398
400
|
if not normalized:
|
|
399
401
|
return None
|
|
400
|
-
|
|
402
|
+
secret_pattern = re.compile(
|
|
403
|
+
r"(?i)\b(password|passwd|api_key|apikey|token|access_token|secret)\s*=\s*"
|
|
404
|
+
r"(\"[^\"]*\"|'[^']*'|[^\s&]+)"
|
|
405
|
+
)
|
|
406
|
+
redacted = secret_pattern.sub(lambda m: f"{m.group(1)}=[REDACTED]", normalized)
|
|
407
|
+
redacted = re.sub(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]+", "Bearer [REDACTED]", redacted)
|
|
408
|
+
return html.escape(redacted, quote=True)[:120]
|
{capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/runtime.py
RENAMED
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
|
+
import copy
|
|
13
14
|
import time
|
|
14
15
|
import uuid
|
|
15
16
|
from dataclasses import replace
|
|
@@ -344,6 +345,7 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
344
345
|
*,
|
|
345
346
|
input: Optional[Dict[str, Any]] = None,
|
|
346
347
|
context: Optional[ExecutionContext] = None,
|
|
348
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
347
349
|
) -> CapabilityResult:
|
|
348
350
|
"""
|
|
349
351
|
非流式执行(等待完成后返回)。
|
|
@@ -352,10 +354,14 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
352
354
|
- capability_id:能力 ID
|
|
353
355
|
- input:输入参数 dict
|
|
354
356
|
- context:可选执行上下文(宿主控制;若不传则由 Runtime 创建)
|
|
357
|
+
- prompt_control:可选 prompt 控制面;优先于兼容入口 `input["_runtime_prompt"]`
|
|
355
358
|
"""
|
|
356
359
|
|
|
357
360
|
result: Optional[CapabilityResult] = None
|
|
358
|
-
|
|
361
|
+
stream_kwargs: Dict[str, Any] = {"input": input, "context": context}
|
|
362
|
+
if prompt_control is not None:
|
|
363
|
+
stream_kwargs["prompt_control"] = prompt_control
|
|
364
|
+
async for item in self.run_stream(capability_id, **stream_kwargs):
|
|
359
365
|
if isinstance(item, CapabilityResult):
|
|
360
366
|
result = item
|
|
361
367
|
if result is None:
|
|
@@ -373,6 +379,7 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
373
379
|
*,
|
|
374
380
|
input: Optional[Dict[str, Any]] = None,
|
|
375
381
|
context: Optional[ExecutionContext] = None,
|
|
382
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
376
383
|
) -> CapabilityResult:
|
|
377
384
|
"""
|
|
378
385
|
强结构输出入口:成功时返回 `dict` 输出。
|
|
@@ -380,11 +387,12 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
380
387
|
约束:
|
|
381
388
|
- 仅支持带 `output_schema` 的 Agent capability;
|
|
382
389
|
- 不改变 `run()` 的既有语义,而是在其之上做强结构收口。
|
|
390
|
+
- `prompt_control` 透传给底层 `run()`,用于显式控制 prompt rendering。
|
|
383
391
|
"""
|
|
384
392
|
|
|
385
393
|
spec = self._registry.get(capability_id)
|
|
386
394
|
if spec is None:
|
|
387
|
-
return await self.run(capability_id, input=input, context=context)
|
|
395
|
+
return await self.run(capability_id, input=input, context=context, prompt_control=prompt_control)
|
|
388
396
|
if not isinstance(spec, AgentSpec):
|
|
389
397
|
run_id = context.run_id if context is not None else uuid.uuid4().hex
|
|
390
398
|
return self._build_fail_closed_result(
|
|
@@ -408,7 +416,7 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
408
416
|
completion_reason="structured_output_schema_missing",
|
|
409
417
|
)
|
|
410
418
|
|
|
411
|
-
result = await self.run(capability_id, input=input, context=context)
|
|
419
|
+
result = await self.run(capability_id, input=input, context=context, prompt_control=prompt_control)
|
|
412
420
|
if result.status != CapabilityStatus.SUCCESS:
|
|
413
421
|
return result
|
|
414
422
|
|
|
@@ -426,6 +434,7 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
426
434
|
*,
|
|
427
435
|
input: Optional[Dict[str, Any]] = None,
|
|
428
436
|
context: Optional[ExecutionContext] = None,
|
|
437
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
429
438
|
) -> AsyncIterator[StructuredStreamEvent]:
|
|
430
439
|
"""
|
|
431
440
|
结构化输出流式消费入口。
|
|
@@ -433,6 +442,7 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
433
442
|
说明:
|
|
434
443
|
- 面向业务代码,不透传 mixed stream / UI events 的全部细节;
|
|
435
444
|
- 仅在观察到 `llm_response_delta(text)` 时产出中途快照与字段更新。
|
|
445
|
+
- `prompt_control` 透传给底层 `run_stream()`,优先于 `input["_runtime_prompt"]`。
|
|
436
446
|
"""
|
|
437
447
|
|
|
438
448
|
spec = self._registry.get(capability_id)
|
|
@@ -474,7 +484,10 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
474
484
|
previous_snapshot: Optional[Dict[str, Any]] = None
|
|
475
485
|
terminal: Optional[CapabilityResult] = None
|
|
476
486
|
|
|
477
|
-
|
|
487
|
+
stream_kwargs: Dict[str, Any] = {"input": input, "context": ctx}
|
|
488
|
+
if prompt_control is not None:
|
|
489
|
+
stream_kwargs["prompt_control"] = prompt_control
|
|
490
|
+
async for item in self.run_stream(capability_id, **stream_kwargs):
|
|
478
491
|
if isinstance(item, CapabilityResult):
|
|
479
492
|
terminal = item
|
|
480
493
|
continue
|
|
@@ -564,6 +577,7 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
564
577
|
*,
|
|
565
578
|
input: Optional[Dict[str, Any]] = None,
|
|
566
579
|
context: Optional[ExecutionContext] = None,
|
|
580
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
567
581
|
) -> AsyncIterator[Union[AgentEvent, WorkflowStreamEvent, CapabilityResult]]:
|
|
568
582
|
"""
|
|
569
583
|
流式执行(执行层混合流):过程中可能产出事件,最后产出终态 CapabilityResult。
|
|
@@ -610,15 +624,30 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
610
624
|
|
|
611
625
|
started = time.monotonic()
|
|
612
626
|
if _get_base(spec).kind == CapabilityKind.AGENT:
|
|
613
|
-
async for x in self._execute_agent_stream(
|
|
627
|
+
async for x in self._execute_agent_stream(
|
|
628
|
+
spec=spec,
|
|
629
|
+
input=input or {},
|
|
630
|
+
context=ctx,
|
|
631
|
+
prompt_control=prompt_control,
|
|
632
|
+
):
|
|
614
633
|
if isinstance(x, CapabilityResult):
|
|
615
|
-
x
|
|
634
|
+
x = replace(
|
|
635
|
+
x,
|
|
636
|
+
duration_ms=(time.monotonic() - started) * 1000,
|
|
637
|
+
artifacts=list(x.artifacts or []),
|
|
638
|
+
metadata=copy.deepcopy(x.metadata or {}),
|
|
639
|
+
)
|
|
616
640
|
yield x
|
|
617
641
|
return
|
|
618
642
|
|
|
619
643
|
async for x in self._execute_workflow_stream(spec=spec, input=input or {}, context=ctx):
|
|
620
644
|
if isinstance(x, CapabilityResult):
|
|
621
|
-
x
|
|
645
|
+
x = replace(
|
|
646
|
+
x,
|
|
647
|
+
duration_ms=(time.monotonic() - started) * 1000,
|
|
648
|
+
artifacts=list(x.artifacts or []),
|
|
649
|
+
metadata=copy.deepcopy(x.metadata or {}),
|
|
650
|
+
)
|
|
622
651
|
yield x
|
|
623
652
|
return
|
|
624
653
|
|
|
@@ -826,7 +855,12 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
826
855
|
yield item
|
|
827
856
|
|
|
828
857
|
async def _execute_agent_stream(
|
|
829
|
-
self,
|
|
858
|
+
self,
|
|
859
|
+
*,
|
|
860
|
+
spec: AnySpec,
|
|
861
|
+
input: Dict[str, Any],
|
|
862
|
+
context: ExecutionContext,
|
|
863
|
+
prompt_control: Optional[Dict[str, Any]] = None,
|
|
830
864
|
) -> AsyncIterator[Union[AgentEvent, CapabilityResult]]:
|
|
831
865
|
"""
|
|
832
866
|
执行 AgentSpec(流式)。
|
|
@@ -849,7 +883,10 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
849
883
|
)
|
|
850
884
|
return
|
|
851
885
|
|
|
852
|
-
|
|
886
|
+
execute_kwargs: Dict[str, Any] = {"spec": spec, "input": input, "context": context}
|
|
887
|
+
if prompt_control is not None:
|
|
888
|
+
execute_kwargs["prompt_control"] = prompt_control
|
|
889
|
+
async for item in self._agent_adapter.execute_stream(**execute_kwargs):
|
|
853
890
|
yield item
|
|
854
891
|
|
|
855
892
|
def build_fail_closed_report(
|
{capability_runtime-0.1.2 → capability_runtime-0.1.3.post1}/src/capability_runtime/sdk_lifecycle.py
RENAMED
|
@@ -19,6 +19,7 @@ from skills_runtime.skills.manager import SkillsManager
|
|
|
19
19
|
from .config import CustomTool, RuntimeConfig, RuntimeMode, normalize_workspace_root
|
|
20
20
|
from .logging_utils import log_suppressed_exception
|
|
21
21
|
from .protocol.chat_backend import ChatBackendProtocol
|
|
22
|
+
from .utils.usage import extract_usage_metrics
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@dataclass(frozen=True)
|
|
@@ -668,7 +669,7 @@ class _UsageTapBackend:
|
|
|
668
669
|
|
|
669
670
|
class _AgentUsageEventBridge:
|
|
670
671
|
"""
|
|
671
|
-
Agent
|
|
672
|
+
Agent 薄代理:补齐 bridge usage metadata,或在底层未主动发出 `llm_usage` 时补发 usage 事件。
|
|
672
673
|
"""
|
|
673
674
|
|
|
674
675
|
def __init__(self, *, agent: Any, usage_bridge_backend: _UsageTapBackend) -> None:
|
|
@@ -683,16 +684,32 @@ class _AgentUsageEventBridge:
|
|
|
683
684
|
initial_history: Optional[List[Dict[str, Any]]] = None,
|
|
684
685
|
) -> AsyncIterator[AgentEvent]:
|
|
685
686
|
"""
|
|
686
|
-
转发 SDK Agent
|
|
687
|
+
转发 SDK Agent 事件流,并按 token usage bridge 规格处理 supplemental usage。
|
|
688
|
+
|
|
689
|
+
参数:
|
|
690
|
+
- task:传给上游 Agent 的任务文本
|
|
691
|
+
- run_id:可选 run 标识
|
|
692
|
+
- initial_history:可选初始历史
|
|
693
|
+
|
|
694
|
+
返回:
|
|
695
|
+
- AgentEvent 异步流;上游 `llm_usage` 存在时只补齐缺失 metadata,不重复补 token
|
|
687
696
|
"""
|
|
688
697
|
|
|
689
698
|
saw_llm_usage = False
|
|
699
|
+
pending_supplemental_usage: List[Dict[str, Any]] = []
|
|
690
700
|
last_run_id = run_id or ""
|
|
691
701
|
last_turn_id: Optional[str] = None
|
|
692
702
|
|
|
693
703
|
async for ev in self._agent.run_stream_async(task, run_id=run_id, initial_history=initial_history):
|
|
694
704
|
if ev.type == "llm_usage":
|
|
695
705
|
saw_llm_usage = True
|
|
706
|
+
pending_supplemental_usage.extend(self._usage_bridge_backend.drain_usage_payloads())
|
|
707
|
+
if pending_supplemental_usage:
|
|
708
|
+
ev = _merge_supplemental_usage_metadata_event(
|
|
709
|
+
ev=ev,
|
|
710
|
+
supplemental_payloads=pending_supplemental_usage,
|
|
711
|
+
)
|
|
712
|
+
pending_supplemental_usage.clear()
|
|
696
713
|
if isinstance(ev.run_id, str) and ev.run_id:
|
|
697
714
|
last_run_id = ev.run_id
|
|
698
715
|
if isinstance(ev.turn_id, str) and ev.turn_id:
|
|
@@ -700,10 +717,12 @@ class _AgentUsageEventBridge:
|
|
|
700
717
|
yield ev
|
|
701
718
|
|
|
702
719
|
if saw_llm_usage:
|
|
720
|
+
# 上游 usage 已是 token 权威来源;清空未配对 payload,避免作为新 usage 事件造成双计。
|
|
703
721
|
self._usage_bridge_backend.drain_usage_payloads()
|
|
704
722
|
return
|
|
705
723
|
|
|
706
|
-
|
|
724
|
+
pending_supplemental_usage.extend(self._usage_bridge_backend.drain_usage_payloads())
|
|
725
|
+
for payload in pending_supplemental_usage:
|
|
707
726
|
yield AgentEvent(
|
|
708
727
|
type="llm_usage",
|
|
709
728
|
timestamp=_now_rfc3339(),
|
|
@@ -719,6 +738,71 @@ def _now_rfc3339() -> str:
|
|
|
719
738
|
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
720
739
|
|
|
721
740
|
|
|
741
|
+
_USAGE_PROVIDER_PLACEHOLDERS = {"openai", "openai-compatible"}
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _merge_supplemental_usage_metadata_event(*, ev: AgentEvent, supplemental_payloads: List[Dict[str, Any]]) -> AgentEvent:
|
|
745
|
+
"""
|
|
746
|
+
把 sink payload 作为 supplemental metadata patch 合并到上游 `llm_usage` 事件。
|
|
747
|
+
|
|
748
|
+
参数:
|
|
749
|
+
- ev:上游 SDK / agent loop 已发出的 `llm_usage` AgentEvent
|
|
750
|
+
- supplemental_payloads:`_caprt_usage_sink` 收集到的 provider/gateway usage payload 列表
|
|
751
|
+
|
|
752
|
+
返回:
|
|
753
|
+
- 新 AgentEvent;仅在上游 metadata 缺失时补 `model/request_id/provider`,不写入 token 字段;
|
|
754
|
+
`provider` 可在上游为 OpenAI-compatible 占位值时被 effective provider 受控覆盖。
|
|
755
|
+
"""
|
|
756
|
+
|
|
757
|
+
upstream_payload = dict(ev.payload) if isinstance(ev.payload, dict) else {}
|
|
758
|
+
merged_payload = dict(upstream_payload)
|
|
759
|
+
|
|
760
|
+
for supplemental_payload in supplemental_payloads:
|
|
761
|
+
supplemental_summary = extract_usage_metrics(supplemental_payload)
|
|
762
|
+
for field in ("model", "request_id"):
|
|
763
|
+
current_value = extract_usage_metrics(merged_payload).get(field)
|
|
764
|
+
if isinstance(current_value, str) and current_value.strip():
|
|
765
|
+
continue
|
|
766
|
+
supplemental_value = supplemental_summary.get(field)
|
|
767
|
+
if isinstance(supplemental_value, str) and supplemental_value.strip():
|
|
768
|
+
merged_payload[field] = supplemental_value.strip()
|
|
769
|
+
current_provider = extract_usage_metrics(merged_payload).get("provider")
|
|
770
|
+
supplemental_provider = supplemental_summary.get("provider")
|
|
771
|
+
if not (isinstance(supplemental_provider, str) and supplemental_provider.strip()):
|
|
772
|
+
continue
|
|
773
|
+
supplemental_provider = supplemental_provider.strip()
|
|
774
|
+
supplemental_provider_is_placeholder = supplemental_provider in _USAGE_PROVIDER_PLACEHOLDERS
|
|
775
|
+
if not (isinstance(current_provider, str) and current_provider.strip()):
|
|
776
|
+
merged_payload["provider"] = supplemental_provider
|
|
777
|
+
continue
|
|
778
|
+
current_provider = current_provider.strip()
|
|
779
|
+
if (
|
|
780
|
+
current_provider in _USAGE_PROVIDER_PLACEHOLDERS
|
|
781
|
+
and not supplemental_provider_is_placeholder
|
|
782
|
+
and current_provider != supplemental_provider
|
|
783
|
+
):
|
|
784
|
+
merged_payload.setdefault("provider_upstream", current_provider)
|
|
785
|
+
merged_payload["provider"] = supplemental_provider
|
|
786
|
+
|
|
787
|
+
if merged_payload == upstream_payload:
|
|
788
|
+
return ev
|
|
789
|
+
try:
|
|
790
|
+
return ev.model_copy(update={"payload": merged_payload})
|
|
791
|
+
except Exception as exc: # pragma: no cover - 当前 AgentEvent 为 Pydantic v2,保留兼容兜底。
|
|
792
|
+
log_suppressed_exception(
|
|
793
|
+
context="supplemental_usage_event_model_copy",
|
|
794
|
+
exc=exc,
|
|
795
|
+
)
|
|
796
|
+
return AgentEvent(
|
|
797
|
+
type=ev.type,
|
|
798
|
+
timestamp=ev.timestamp,
|
|
799
|
+
run_id=ev.run_id,
|
|
800
|
+
turn_id=ev.turn_id,
|
|
801
|
+
step_id=ev.step_id,
|
|
802
|
+
payload=merged_payload,
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
|
|
722
806
|
def _merge_usage_sink(*, extra: Dict[str, Any], sink: Any) -> Dict[str, Any]:
|
|
723
807
|
"""
|
|
724
808
|
合并 `_caprt_usage_sink` 回调,保留已有 sink 并保证两者都被调用。
|
|
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
"""结构化输出桥接:`output_schema` 校验、摘要留痕与结果收敛。"""
|
|
4
4
|
|
|
5
|
+
import copy
|
|
5
6
|
import hashlib
|
|
6
7
|
import json
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
+
from dataclasses import dataclass, replace
|
|
8
9
|
from typing import Any, Dict, List, Optional
|
|
9
10
|
|
|
10
11
|
from .config import OutputValidationMode
|
|
@@ -183,26 +184,51 @@ def finalize_structured_result(
|
|
|
183
184
|
validation: StructuredOutputValidation,
|
|
184
185
|
fail_on_error: bool,
|
|
185
186
|
) -> CapabilityResult:
|
|
186
|
-
"""把普通 CapabilityResult
|
|
187
|
+
"""把普通 CapabilityResult 收敛为结构化结果,并返回新的 terminal 对象。"""
|
|
187
188
|
|
|
188
|
-
result.metadata
|
|
189
|
-
|
|
189
|
+
metadata = copy.deepcopy(result.metadata or {})
|
|
190
|
+
metadata["raw_output"] = validation.raw_output
|
|
191
|
+
metadata["structured_output"] = dict(validation.summary)
|
|
190
192
|
|
|
191
|
-
if result.node_report is not None
|
|
193
|
+
node_report = copy.deepcopy(result.node_report) if result.node_report is not None else None
|
|
194
|
+
if node_report is not None:
|
|
192
195
|
apply_structured_output_summary(
|
|
193
|
-
report=
|
|
196
|
+
report=node_report,
|
|
194
197
|
validation=validation,
|
|
195
198
|
fail_on_error=fail_on_error,
|
|
196
199
|
)
|
|
197
200
|
|
|
201
|
+
if result.report is result.node_report:
|
|
202
|
+
report = node_report
|
|
203
|
+
else:
|
|
204
|
+
report = copy.deepcopy(result.report) if result.report is not None else None
|
|
205
|
+
if isinstance(report, NodeReport):
|
|
206
|
+
apply_structured_output_summary(
|
|
207
|
+
report=report,
|
|
208
|
+
validation=validation,
|
|
209
|
+
fail_on_error=fail_on_error,
|
|
210
|
+
)
|
|
211
|
+
|
|
198
212
|
if not validation.ok:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
return replace(
|
|
214
|
+
result,
|
|
215
|
+
status=CapabilityStatus.FAILED,
|
|
216
|
+
output=None,
|
|
217
|
+
error="Structured output contract violated",
|
|
218
|
+
error_code="STRUCTURED_OUTPUT_INVALID",
|
|
219
|
+
report=report,
|
|
220
|
+
node_report=node_report,
|
|
221
|
+
artifacts=list(result.artifacts or []),
|
|
222
|
+
metadata=metadata,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return replace(
|
|
226
|
+
result,
|
|
227
|
+
output=dict(validation.normalized_output or {}),
|
|
228
|
+
error=None,
|
|
229
|
+
error_code=None,
|
|
230
|
+
report=report,
|
|
231
|
+
node_report=node_report,
|
|
232
|
+
artifacts=list(result.artifacts or []),
|
|
233
|
+
metadata=metadata,
|
|
234
|
+
)
|
|
@@ -111,6 +111,7 @@ tests/test_runtime_hitl_host_protocol.py
|
|
|
111
111
|
tests/test_runtime_hooks_and_schema_gate.py
|
|
112
112
|
tests/test_runtime_initial_history_and_meta.py
|
|
113
113
|
tests/test_runtime_manifest.py
|
|
114
|
+
tests/test_runtime_result_prompt_hardening.py
|
|
114
115
|
tests/test_runtime_run_stream_semantics_v1.py
|
|
115
116
|
tests/test_runtime_service_facade_rpc_mapping.py
|
|
116
117
|
tests/test_runtime_service_session_bridge.py
|