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.
- capability_runtime/__init__.py +90 -0
- capability_runtime/adapters/__init__.py +13 -0
- capability_runtime/adapters/agent_adapter.py +439 -0
- capability_runtime/adapters/agently_backend.py +423 -0
- capability_runtime/adapters/triggerflow_workflow_engine.py +865 -0
- capability_runtime/adapters/workflow_engine.py +43 -0
- capability_runtime/config.py +172 -0
- capability_runtime/errors.py +20 -0
- capability_runtime/guards.py +150 -0
- capability_runtime/host_protocol.py +400 -0
- capability_runtime/host_toolkit/__init__.py +55 -0
- capability_runtime/host_toolkit/approvals_profiles.py +94 -0
- capability_runtime/host_toolkit/evidence_hooks.py +65 -0
- capability_runtime/host_toolkit/history.py +74 -0
- capability_runtime/host_toolkit/invoke_capability.py +409 -0
- capability_runtime/host_toolkit/resume.py +317 -0
- capability_runtime/host_toolkit/system_prompt.py +132 -0
- capability_runtime/host_toolkit/turn_delta.py +128 -0
- capability_runtime/logging_utils.py +94 -0
- capability_runtime/manifest.py +173 -0
- capability_runtime/output_validator.py +139 -0
- capability_runtime/protocol/__init__.py +43 -0
- capability_runtime/protocol/agent.py +62 -0
- capability_runtime/protocol/capability.py +98 -0
- capability_runtime/protocol/chat_backend.py +38 -0
- capability_runtime/protocol/context.py +244 -0
- capability_runtime/protocol/workflow.py +119 -0
- capability_runtime/registry.py +287 -0
- capability_runtime/reporting/__init__.py +2 -0
- capability_runtime/reporting/node_report.py +497 -0
- capability_runtime/runtime.py +930 -0
- capability_runtime/runtime_ui_events_mixin.py +310 -0
- capability_runtime/sdk_lifecycle.py +982 -0
- capability_runtime/service_facade.py +418 -0
- capability_runtime/services.py +181 -0
- capability_runtime/structured_output.py +208 -0
- capability_runtime/structured_stream.py +38 -0
- capability_runtime/types.py +103 -0
- capability_runtime/ui_events/__init__.py +19 -0
- capability_runtime/ui_events/projector.py +617 -0
- capability_runtime/ui_events/session.py +292 -0
- capability_runtime/ui_events/store.py +127 -0
- capability_runtime/ui_events/transport.py +33 -0
- capability_runtime/ui_events/v1.py +76 -0
- capability_runtime/upstream_compat.py +182 -0
- capability_runtime/utils/__init__.py +1 -0
- capability_runtime/utils/usage.py +65 -0
- capability_runtime/workflow_runtime.py +218 -0
- capability_runtime-0.1.0.dist-info/METADATA +232 -0
- capability_runtime-0.1.0.dist-info/RECORD +52 -0
- capability_runtime-0.1.0.dist-info/WHEEL +5 -0
- 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
|
+
]
|