capability-runtime 0.1.0__py3-none-any.whl

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 (52) hide show
  1. capability_runtime/__init__.py +90 -0
  2. capability_runtime/adapters/__init__.py +13 -0
  3. capability_runtime/adapters/agent_adapter.py +439 -0
  4. capability_runtime/adapters/agently_backend.py +423 -0
  5. capability_runtime/adapters/triggerflow_workflow_engine.py +865 -0
  6. capability_runtime/adapters/workflow_engine.py +43 -0
  7. capability_runtime/config.py +172 -0
  8. capability_runtime/errors.py +20 -0
  9. capability_runtime/guards.py +150 -0
  10. capability_runtime/host_protocol.py +400 -0
  11. capability_runtime/host_toolkit/__init__.py +55 -0
  12. capability_runtime/host_toolkit/approvals_profiles.py +94 -0
  13. capability_runtime/host_toolkit/evidence_hooks.py +65 -0
  14. capability_runtime/host_toolkit/history.py +74 -0
  15. capability_runtime/host_toolkit/invoke_capability.py +409 -0
  16. capability_runtime/host_toolkit/resume.py +317 -0
  17. capability_runtime/host_toolkit/system_prompt.py +132 -0
  18. capability_runtime/host_toolkit/turn_delta.py +128 -0
  19. capability_runtime/logging_utils.py +94 -0
  20. capability_runtime/manifest.py +173 -0
  21. capability_runtime/output_validator.py +139 -0
  22. capability_runtime/protocol/__init__.py +43 -0
  23. capability_runtime/protocol/agent.py +62 -0
  24. capability_runtime/protocol/capability.py +98 -0
  25. capability_runtime/protocol/chat_backend.py +38 -0
  26. capability_runtime/protocol/context.py +244 -0
  27. capability_runtime/protocol/workflow.py +119 -0
  28. capability_runtime/registry.py +287 -0
  29. capability_runtime/reporting/__init__.py +2 -0
  30. capability_runtime/reporting/node_report.py +497 -0
  31. capability_runtime/runtime.py +930 -0
  32. capability_runtime/runtime_ui_events_mixin.py +310 -0
  33. capability_runtime/sdk_lifecycle.py +982 -0
  34. capability_runtime/service_facade.py +418 -0
  35. capability_runtime/services.py +181 -0
  36. capability_runtime/structured_output.py +208 -0
  37. capability_runtime/structured_stream.py +38 -0
  38. capability_runtime/types.py +103 -0
  39. capability_runtime/ui_events/__init__.py +19 -0
  40. capability_runtime/ui_events/projector.py +617 -0
  41. capability_runtime/ui_events/session.py +292 -0
  42. capability_runtime/ui_events/store.py +127 -0
  43. capability_runtime/ui_events/transport.py +33 -0
  44. capability_runtime/ui_events/v1.py +76 -0
  45. capability_runtime/upstream_compat.py +182 -0
  46. capability_runtime/utils/__init__.py +1 -0
  47. capability_runtime/utils/usage.py +65 -0
  48. capability_runtime/workflow_runtime.py +218 -0
  49. capability_runtime-0.1.0.dist-info/METADATA +232 -0
  50. capability_runtime-0.1.0.dist-info/RECORD +52 -0
  51. capability_runtime-0.1.0.dist-info/WHEEL +5 -0
  52. capability_runtime-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,208 @@
1
+ from __future__ import annotations
2
+
3
+ """结构化输出桥接:`output_schema` 校验、摘要留痕与结果收敛。"""
4
+
5
+ import hashlib
6
+ import json
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from .config import OutputValidationMode
11
+ from .logging_utils import log_suppressed_exception
12
+ from .protocol.agent import AgentIOSchema
13
+ from .protocol.capability import CapabilityResult, CapabilityStatus
14
+ from .types import NodeReport
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class StructuredOutputValidation:
19
+ """一次结构化输出校验的收敛结果。"""
20
+
21
+ ok: bool
22
+ raw_output: str
23
+ normalized_output: Optional[Dict[str, Any]]
24
+ summary: Dict[str, Any]
25
+
26
+
27
+ def schema_id_for_capability(*, capability_id: str) -> str:
28
+ """为 capability 生成稳定的结构化输出 schema_id。"""
29
+
30
+ return f"capability-runtime.agent_output_schema.v1:{capability_id}"
31
+
32
+
33
+ def parse_json_object_snapshot(text: str) -> Optional[Dict[str, Any]]:
34
+ """
35
+ 尝试把累计文本解析为 JSON object 快照。
36
+
37
+ 说明:
38
+ - 只接受顶层 object;
39
+ - 失败时返回 None,不做猜测性修复。
40
+ """
41
+
42
+ try:
43
+ payload = json.loads(str(text or ""))
44
+ except Exception as exc:
45
+ log_suppressed_exception(
46
+ context="parse_structured_output_json",
47
+ exc=exc,
48
+ extra={"text_len": len(str(text or ""))},
49
+ )
50
+ return None
51
+ return dict(payload) if isinstance(payload, dict) else None
52
+
53
+
54
+ def _digest_payload(payload: Dict[str, Any]) -> Dict[str, Any]:
55
+ """把 payload 归一为最小披露摘要。"""
56
+
57
+ payload_text = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
58
+ return {
59
+ "normalized_payload_sha256": hashlib.sha256(payload_text.encode("utf-8")).hexdigest(),
60
+ "normalized_payload_bytes": len(payload_text.encode("utf-8")),
61
+ "normalized_payload_top_keys": sorted(list(payload.keys()))[:20],
62
+ }
63
+
64
+
65
+ def validate_structured_output(
66
+ *,
67
+ final_output: Any,
68
+ output_schema: AgentIOSchema,
69
+ capability_id: str,
70
+ mode: OutputValidationMode,
71
+ ) -> StructuredOutputValidation:
72
+ """
73
+ 基于 `AgentIOSchema` 校验终态输出。
74
+
75
+ v1 语义:
76
+ - 只接受顶层 JSON object
77
+ - 只校验 required fields
78
+ - 不拒绝额外字段
79
+ - 不强做类型校验(`fields` 仍是描述字符串)
80
+ """
81
+
82
+ required = [str(name) for name in list(output_schema.required or []) if str(name)]
83
+ summary: Dict[str, Any] = {
84
+ "mode": mode,
85
+ "ok": False,
86
+ "schema_id": schema_id_for_capability(capability_id=capability_id),
87
+ "required": required,
88
+ }
89
+
90
+ if isinstance(final_output, dict):
91
+ normalized = dict(final_output)
92
+ raw_output = json.dumps(normalized, ensure_ascii=False, separators=(",", ":"))
93
+ else:
94
+ raw_output = "" if final_output is None else str(final_output)
95
+ try:
96
+ parsed = json.loads(raw_output)
97
+ except Exception as exc:
98
+ log_suppressed_exception(
99
+ context="validate_structured_output_parse",
100
+ exc=exc,
101
+ extra={"raw_output_len": len(raw_output)},
102
+ )
103
+ summary["errors"] = [
104
+ {
105
+ "path": "$",
106
+ "kind": "invalid_json",
107
+ "message": "final_output is not valid JSON object",
108
+ }
109
+ ]
110
+ return StructuredOutputValidation(
111
+ ok=False,
112
+ raw_output=raw_output,
113
+ normalized_output=None,
114
+ summary=summary,
115
+ )
116
+ if not isinstance(parsed, dict):
117
+ summary["errors"] = [
118
+ {
119
+ "path": "$",
120
+ "kind": "non_object",
121
+ "message": "final_output must be a top-level JSON object",
122
+ }
123
+ ]
124
+ return StructuredOutputValidation(
125
+ ok=False,
126
+ raw_output=raw_output,
127
+ normalized_output=None,
128
+ summary=summary,
129
+ )
130
+ normalized = dict(parsed)
131
+
132
+ present_keys = sorted(list(normalized.keys()))
133
+ summary["present_keys"] = present_keys
134
+ summary.update(_digest_payload(normalized))
135
+
136
+ errors: List[Dict[str, str]] = []
137
+ for field in required:
138
+ if field not in normalized:
139
+ errors.append(
140
+ {
141
+ "path": f"$.{field}",
142
+ "kind": "missing_required",
143
+ "message": f"{field} is required",
144
+ }
145
+ )
146
+ if errors:
147
+ summary["errors"] = errors
148
+ return StructuredOutputValidation(
149
+ ok=False,
150
+ raw_output=raw_output,
151
+ normalized_output=normalized,
152
+ summary=summary,
153
+ )
154
+
155
+ summary["ok"] = True
156
+ summary["errors"] = []
157
+ return StructuredOutputValidation(
158
+ ok=True,
159
+ raw_output=raw_output,
160
+ normalized_output=normalized,
161
+ summary=summary,
162
+ )
163
+
164
+
165
+ def apply_structured_output_summary(
166
+ *,
167
+ report: NodeReport,
168
+ validation: StructuredOutputValidation,
169
+ fail_on_error: bool,
170
+ ) -> None:
171
+ """把结构化输出摘要写入 NodeReport,并在需要时 fail-closed。"""
172
+
173
+ report.meta["structured_output"] = dict(validation.summary)
174
+ if (not validation.ok) and fail_on_error:
175
+ report.status = "failed"
176
+ report.reason = "structured_output_error"
177
+ report.meta["structured_output_overrode_status"] = True
178
+
179
+
180
+ def finalize_structured_result(
181
+ *,
182
+ result: CapabilityResult,
183
+ validation: StructuredOutputValidation,
184
+ fail_on_error: bool,
185
+ ) -> CapabilityResult:
186
+ """把普通 CapabilityResult 收敛为结构化结果。"""
187
+
188
+ result.metadata["raw_output"] = validation.raw_output
189
+ result.metadata["structured_output"] = dict(validation.summary)
190
+
191
+ if result.node_report is not None:
192
+ apply_structured_output_summary(
193
+ report=result.node_report,
194
+ validation=validation,
195
+ fail_on_error=fail_on_error,
196
+ )
197
+
198
+ if not validation.ok:
199
+ result.status = CapabilityStatus.FAILED
200
+ result.output = None
201
+ result.error = "Structured output contract violated"
202
+ result.error_code = "STRUCTURED_OUTPUT_INVALID"
203
+ return result
204
+
205
+ result.output = dict(validation.normalized_output or {})
206
+ result.error = None
207
+ result.error_code = None
208
+ return result
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ """结构化流式消费的公开事件模型。"""
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class StructuredStreamEvent:
11
+ """业务友好的低噪音结构化流事件。"""
12
+
13
+ type: str
14
+ run_id: str
15
+ capability_id: str
16
+ text: Optional[str] = None
17
+ field: Optional[str] = None
18
+ value: Any = None
19
+ snapshot: Optional[Dict[str, Any]] = None
20
+ status: Optional[str] = None
21
+ output: Optional[Dict[str, Any]] = None
22
+ raw_output: Optional[str] = None
23
+ error: Optional[str] = None
24
+ error_code: Optional[str] = None
25
+
26
+
27
+ def diff_top_level_fields(
28
+ previous: Optional[Dict[str, Any]],
29
+ current: Dict[str, Any],
30
+ ) -> List[Tuple[str, Any]]:
31
+ """计算顶层字段差异;仅输出新增或值发生变化的字段。"""
32
+
33
+ previous = dict(previous or {})
34
+ changed: List[Tuple[str, Any]] = []
35
+ for key in current.keys():
36
+ if previous.get(key) != current.get(key):
37
+ changed.append((key, current.get(key)))
38
+ return changed
@@ -0,0 +1,103 @@
1
+ """
2
+ 桥接层对外数据结构(NodeReport / NodeResult)。
3
+
4
+ 说明:
5
+ - NodeReport 是“控制面强结构”输出,供 Workflow/业务编排做分支/审计/回归。
6
+ - NodeResult 是一次运行的对外返回值:final_output + node_report + events_path。
7
+
8
+ 对齐规格:
9
+ - `openspec/specs/evidence-chain/spec.md`
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, Dict, List, Literal, Optional
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field
17
+
18
+
19
+ NodeStatus = Literal["success", "failed", "incomplete", "needs_approval"]
20
+
21
+
22
+ class NodeToolCallReport(BaseModel):
23
+ """
24
+ NodeReport.tool_calls 的最小条目。
25
+
26
+ 字段说明:
27
+ - `data`:优先放入“可机器消费”的结构化 data(来自 ToolResultPayload.data),避免塞 stdout/stderr。
28
+ """
29
+
30
+ model_config = ConfigDict(extra="forbid")
31
+
32
+ call_id: str
33
+ name: str
34
+ requires_approval: bool = False
35
+
36
+ approval_key: Optional[str] = None
37
+ approval_decision: Optional[Literal["approved", "approved_for_session", "denied", "abort"]] = None
38
+ approval_reason: Optional[str] = None
39
+
40
+ ok: bool = False
41
+ error_kind: Optional[str] = None
42
+ data: Optional[Dict[str, Any]] = None
43
+
44
+
45
+ class NodeUsageReport(BaseModel):
46
+ """
47
+ NodeReport.usage 的最小摘要。
48
+
49
+ 字段说明:
50
+ - `model`:best-effort 记录本次运行最后一次可识别的模型名。
51
+ - token 字段缺失时保持 None,禁止伪造 0。
52
+ """
53
+
54
+ model_config = ConfigDict(extra="forbid")
55
+
56
+ model: Optional[str] = None
57
+ input_tokens: Optional[int] = None
58
+ output_tokens: Optional[int] = None
59
+ total_tokens: Optional[int] = None
60
+
61
+
62
+ class NodeReport(BaseModel):
63
+ """NodeReport(控制面强结构,schema v1)。"""
64
+
65
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
66
+
67
+ schema_id: str = Field(default="capability-runtime.node_report.v1", alias="schema")
68
+ status: NodeStatus
69
+ reason: Optional[str] = None
70
+ completion_reason: str = ""
71
+
72
+ engine: Dict[str, Any] = Field(default_factory=dict)
73
+ bridge: Dict[str, Any] = Field(default_factory=dict)
74
+
75
+ run_id: str
76
+ turn_id: Optional[str] = None
77
+ events_path: Optional[str] = None
78
+
79
+ usage: Optional[NodeUsageReport] = None
80
+ activated_skills: List[str] = Field(default_factory=list)
81
+ tool_calls: List[NodeToolCallReport] = Field(default_factory=list)
82
+ artifacts: List[str] = Field(default_factory=list)
83
+
84
+ meta: Dict[str, Any] = Field(default_factory=dict)
85
+
86
+
87
+ class NodeResult(BaseModel):
88
+ """
89
+ 桥接层一次运行的返回值。
90
+
91
+ 字段:
92
+ - final_output:面向用户的数据面输出(可能为自由文本)
93
+ - node_report:控制面强结构(可编排、可审计)
94
+ - events_path:SDK WAL 定位符(locator;上游 `skills-runtime-sdk>=1.0` 使用 `wal_locator`,本仓对外沿用 `events_path` 命名;不得伪造)
95
+ - artifacts:产物路径列表(Phase 2 可能为空,但字段必须保留)
96
+ """
97
+
98
+ model_config = ConfigDict(extra="forbid")
99
+
100
+ final_output: str
101
+ node_report: NodeReport
102
+ events_path: Optional[str] = None
103
+ artifacts: List[str] = Field(default_factory=list)
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from .projector import RuntimeUIEventProjector
4
+ from .session import RuntimeUIEventsSession
5
+ from .store import AfterIdExpiredError, InMemoryRuntimeEventStore
6
+ from .transport import encode_json_line
7
+ from .v1 import Evidence, PathSegment, RuntimeEvent, StreamLevel
8
+
9
+ __all__ = [
10
+ "AfterIdExpiredError",
11
+ "InMemoryRuntimeEventStore",
12
+ "encode_json_line",
13
+ "Evidence",
14
+ "PathSegment",
15
+ "RuntimeEvent",
16
+ "StreamLevel",
17
+ "RuntimeUIEventProjector",
18
+ "RuntimeUIEventsSession",
19
+ ]