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,43 @@
|
|
|
1
|
+
"""Workflow 引擎内部协议。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, AsyncIterator, Dict, Protocol, Union
|
|
5
|
+
|
|
6
|
+
from ..protocol.capability import CapabilityResult
|
|
7
|
+
from ..protocol.context import ExecutionContext
|
|
8
|
+
from ..protocol.workflow import WorkflowSpec
|
|
9
|
+
from ..services import RuntimeServices
|
|
10
|
+
|
|
11
|
+
# Workflow 轻量流式事件:默认给编排/UI 读取,不承担深审计职责。
|
|
12
|
+
WorkflowStreamEvent = Dict[str, Any]
|
|
13
|
+
WorkflowStreamItem = Union[WorkflowStreamEvent, CapabilityResult]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WorkflowEngine(Protocol):
|
|
17
|
+
"""
|
|
18
|
+
Workflow 引擎协议(内部使用)。
|
|
19
|
+
|
|
20
|
+
约束:
|
|
21
|
+
- 对外 API 仍由 Runtime 暴露;
|
|
22
|
+
- 引擎可替换,但 execute/execute_stream 契约必须稳定。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
async def execute(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
spec: WorkflowSpec,
|
|
29
|
+
input: Dict[str, Any],
|
|
30
|
+
context: ExecutionContext,
|
|
31
|
+
services: RuntimeServices,
|
|
32
|
+
) -> CapabilityResult:
|
|
33
|
+
"""执行 Workflow(非流式)。"""
|
|
34
|
+
|
|
35
|
+
async def execute_stream(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
spec: WorkflowSpec,
|
|
39
|
+
input: Dict[str, Any],
|
|
40
|
+
context: ExecutionContext,
|
|
41
|
+
services: RuntimeServices,
|
|
42
|
+
) -> AsyncIterator[WorkflowStreamItem]:
|
|
43
|
+
"""执行 Workflow(流式)。"""
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
统一运行时配置(RuntimeConfig)。
|
|
5
|
+
|
|
6
|
+
说明:
|
|
7
|
+
- 本仓定位为 runtime/adapter/bridge 的契约收敛层;
|
|
8
|
+
- 业务不应在本仓中被定义或侵入;
|
|
9
|
+
- 执行模式通过 `RuntimeConfig.mode` 切换,而不是通过更换入口类。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Callable, Dict, List, Literal, Optional
|
|
15
|
+
|
|
16
|
+
from skills_runtime.core.exec_sessions import ExecSessionsProvider
|
|
17
|
+
from skills_runtime.llm.protocol import ChatBackend
|
|
18
|
+
from skills_runtime.safety.approvals import ApprovalProvider
|
|
19
|
+
from skills_runtime.state.wal_protocol import WalBackend
|
|
20
|
+
from skills_runtime.tools.protocol import HumanIOProvider, ToolSpec
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
PreflightMode = Literal["error", "warn", "off"]
|
|
24
|
+
RuntimeMode = Literal["mock", "bridge", "sdk_native"]
|
|
25
|
+
OutputValidationMode = Literal["off", "warn", "error"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class CustomTool:
|
|
30
|
+
"""
|
|
31
|
+
预注册的自定义工具(每次创建 SDK Agent 时注入)。
|
|
32
|
+
|
|
33
|
+
参数:
|
|
34
|
+
- spec:工具规格(ToolSpec)
|
|
35
|
+
- handler:工具处理函数(签名需兼容上游 ToolRegistry)
|
|
36
|
+
- override:是否允许覆盖同名工具
|
|
37
|
+
- descriptor:可选上游 tool descriptor(若注册签名支持则透传)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
spec: ToolSpec
|
|
41
|
+
handler: Any
|
|
42
|
+
override: bool = False
|
|
43
|
+
descriptor: Any | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class RuntimeConfig:
|
|
48
|
+
"""
|
|
49
|
+
统一运行时配置。
|
|
50
|
+
|
|
51
|
+
参数分组:
|
|
52
|
+
- 执行模式:mode
|
|
53
|
+
- 桥接执行:workspace_root / sdk_config_paths / agently_agent
|
|
54
|
+
- Workflow:workflow_engine(可注入)
|
|
55
|
+
- SDK 注入:approval_provider / human_io / cancel_checker / wal_backend / env_vars
|
|
56
|
+
- Skills 配置:skills_config / in_memory_skills
|
|
57
|
+
- 自定义工具:custom_tools
|
|
58
|
+
- 护栏:max_depth / max_total_loop_iterations / preflight_mode
|
|
59
|
+
- Mock:mock_handler
|
|
60
|
+
- 观测:on_event
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# === 执行模式 ===
|
|
64
|
+
mode: RuntimeMode = "bridge"
|
|
65
|
+
|
|
66
|
+
# === 桥接配置(mode=bridge 时使用)===
|
|
67
|
+
workspace_root: Optional[Path] = None
|
|
68
|
+
sdk_config_paths: List[Path] = field(default_factory=list)
|
|
69
|
+
agently_agent: Optional[Any] = None
|
|
70
|
+
|
|
71
|
+
# === Workflow 引擎注入(可选)===
|
|
72
|
+
#
|
|
73
|
+
# 说明:
|
|
74
|
+
# - 默认使用 TriggerFlowWorkflowEngine(Agently TriggerFlow);
|
|
75
|
+
# - 当上游 workflow engine 需要替换/隔离时,可注入兼容接口的实现:
|
|
76
|
+
# - execute(*, spec, input, context, runtime) -> CapabilityResult
|
|
77
|
+
# - execute_stream(*, spec, input, context, runtime) -> AsyncIterator[WorkflowStreamItem]
|
|
78
|
+
workflow_engine: Optional[Any] = None
|
|
79
|
+
|
|
80
|
+
# === 可选 Runtime RPC client/server 注入(v1)===
|
|
81
|
+
#
|
|
82
|
+
# 说明:
|
|
83
|
+
# - `runtime_client`:供 RuntimeServiceFacade 在 RPC 执行目标下使用;
|
|
84
|
+
# - `runtime_server`:供 Runtime.bind_runtime_server() 显式绑定本地 Runtime;
|
|
85
|
+
# - 两者均采用 duck typing,不在 RuntimeConfig 层做强类型约束。
|
|
86
|
+
runtime_client: Optional[Any] = None
|
|
87
|
+
runtime_server: Optional[Any] = None
|
|
88
|
+
|
|
89
|
+
# === SDK 注入 ===
|
|
90
|
+
approval_provider: Optional[ApprovalProvider] = None
|
|
91
|
+
human_io: Optional[HumanIOProvider] = None
|
|
92
|
+
cancel_checker: Optional[Callable[[], bool]] = None
|
|
93
|
+
exec_sessions: Optional[ExecSessionsProvider] = None
|
|
94
|
+
collab_manager: Optional[object] = None
|
|
95
|
+
wal_backend: Optional[WalBackend] = None
|
|
96
|
+
env_vars: Dict[str, str] = field(default_factory=dict)
|
|
97
|
+
|
|
98
|
+
# === SDK LLM backend 注入(离线回归/测试)===
|
|
99
|
+
#
|
|
100
|
+
# 说明:
|
|
101
|
+
# - 默认情况下 backend 由 mode 决定:
|
|
102
|
+
# - bridge:AgentlyChatBackend(复用 OpenAICompatible requester 作为传输层)
|
|
103
|
+
# - sdk_native:skills_runtime 原生 OpenAIChatCompletionsBackend
|
|
104
|
+
# - 当你需要离线可回归(不依赖外网/真实 key),可注入 FakeChatBackend 等实现驱动真实 Agent loop,
|
|
105
|
+
# 以获得完整的 tool/approvals/WAL/NodeReport 证据链。
|
|
106
|
+
# - 该字段仅改变“LLM 传输层”,不改变 tool/skills/WAL 的执行真相源(仍为 skills_runtime.Agent)。
|
|
107
|
+
sdk_backend: Optional[ChatBackend] = None
|
|
108
|
+
|
|
109
|
+
# === Skills 配置 ===
|
|
110
|
+
skills_config: Optional[Dict[str, Any]] = None
|
|
111
|
+
in_memory_skills: Optional[Dict[str, List[dict]]] = None
|
|
112
|
+
|
|
113
|
+
# === 自定义 Tools ===
|
|
114
|
+
custom_tools: List[CustomTool] = field(default_factory=list)
|
|
115
|
+
|
|
116
|
+
# === 护栏 ===
|
|
117
|
+
max_depth: int = 10
|
|
118
|
+
max_total_loop_iterations: int = 50000
|
|
119
|
+
preflight_mode: PreflightMode = "error"
|
|
120
|
+
|
|
121
|
+
# === Output Validator(可选)===
|
|
122
|
+
#
|
|
123
|
+
# 说明:
|
|
124
|
+
# - 取代旧的 SchemaGate/SchemaGateMode:以 callback 方式提供输出校验;
|
|
125
|
+
# - callback 返回值应是“可观测摘要”,不得把大段 payload 原文塞入 meta;
|
|
126
|
+
# - 推荐签名(keyword-only):
|
|
127
|
+
# validate(*, final_output: str, node_report: NodeReport, context: dict) -> dict
|
|
128
|
+
output_validation_mode: OutputValidationMode = "off"
|
|
129
|
+
output_validator: Optional[Callable[..., Any]] = None
|
|
130
|
+
|
|
131
|
+
# === Mock(mode=mock 时使用)===
|
|
132
|
+
#
|
|
133
|
+
# 说明:
|
|
134
|
+
# - 用于离线回归与场景测试;
|
|
135
|
+
# - 允许两种签名(二选一):
|
|
136
|
+
# 1) handler(spec, input_dict) -> Any | CapabilityResult
|
|
137
|
+
# 2) handler(spec, input_dict, context) -> Any | CapabilityResult
|
|
138
|
+
# - 返回 CapabilityResult 时 Runtime 将直接透传(便于测试 pending/failed/report 语义)。
|
|
139
|
+
mock_handler: Optional[Callable[..., Any]] = None
|
|
140
|
+
|
|
141
|
+
# === 事件回调(可观测性)===
|
|
142
|
+
#
|
|
143
|
+
# 说明:
|
|
144
|
+
# - 允许两种签名(二选一):
|
|
145
|
+
# 1) on_event(event) -> None
|
|
146
|
+
# 2) on_event(event, context) -> None
|
|
147
|
+
# - 回调异常将被吞掉(fail-open),避免影响主流程。
|
|
148
|
+
on_event: Optional[Callable[..., None]] = None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def normalize_workspace_root(workspace_root: Optional[Path]) -> Path:
|
|
152
|
+
"""
|
|
153
|
+
归一化 workspace_root。
|
|
154
|
+
|
|
155
|
+
参数:
|
|
156
|
+
- workspace_root:可选 Path;为 None 时默认当前目录
|
|
157
|
+
|
|
158
|
+
返回:
|
|
159
|
+
- 绝对路径 Path
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
return (workspace_root or Path(".")).expanduser().resolve()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
__all__ = [
|
|
166
|
+
"PreflightMode",
|
|
167
|
+
"RuntimeMode",
|
|
168
|
+
"OutputValidationMode",
|
|
169
|
+
"CustomTool",
|
|
170
|
+
"RuntimeConfig",
|
|
171
|
+
"normalize_workspace_root",
|
|
172
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""框架统一错误定义。"""
|
|
4
|
+
|
|
5
|
+
from .protocol.context import RecursionLimitError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RuntimeFrameworkError(Exception):
|
|
9
|
+
"""框架基础错误。"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CapabilityNotFoundError(RuntimeFrameworkError):
|
|
13
|
+
"""指定 ID 的能力未注册。"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"RuntimeFrameworkError",
|
|
18
|
+
"CapabilityNotFoundError",
|
|
19
|
+
"RecursionLimitError",
|
|
20
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""执行守卫——全局循环与编排保护。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import replace
|
|
5
|
+
from typing import Any, Awaitable, Callable, Dict, List
|
|
6
|
+
|
|
7
|
+
from .protocol.capability import CapabilityResult, CapabilityStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoopBreakerError(Exception):
|
|
11
|
+
"""全局循环迭代次数超限。"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExecutionGuards:
|
|
15
|
+
"""
|
|
16
|
+
全局执行守卫(per-run)。
|
|
17
|
+
|
|
18
|
+
职责:
|
|
19
|
+
- 维护“全局循环迭代次数”计数器,用于熔断失控的 Loop;
|
|
20
|
+
- 提供 LoopStep 的确定性执行方法(`run_loop`),并在每次迭代调用 `tick()`。
|
|
21
|
+
|
|
22
|
+
说明:
|
|
23
|
+
- 该守卫是 `LoopStep.max_iterations` 之上的第二道防线;
|
|
24
|
+
- 调用方应在每次顶层 run 开始前调用 `reset()`,避免跨 run 串扰。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, *, max_total_loop_iterations: int = 50000):
|
|
28
|
+
self._max = max_total_loop_iterations
|
|
29
|
+
self._counter = 0
|
|
30
|
+
|
|
31
|
+
def tick(self) -> None:
|
|
32
|
+
"""
|
|
33
|
+
记录一次循环迭代。
|
|
34
|
+
|
|
35
|
+
超过全局上限时抛出 LoopBreakerError(fail-fast)。
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
self._counter += 1
|
|
39
|
+
if self._counter > self._max:
|
|
40
|
+
raise LoopBreakerError(
|
|
41
|
+
f"Global loop iteration limit ({self._max}) exceeded. "
|
|
42
|
+
f"Total iterations so far: {self._counter}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def counter(self) -> int:
|
|
47
|
+
"""当前累计迭代次数。"""
|
|
48
|
+
|
|
49
|
+
return self._counter
|
|
50
|
+
|
|
51
|
+
def reset(self) -> None:
|
|
52
|
+
"""重置计数器(通常在新的顶层 run 时调用)。"""
|
|
53
|
+
|
|
54
|
+
self._counter = 0
|
|
55
|
+
|
|
56
|
+
async def run_loop(
|
|
57
|
+
self,
|
|
58
|
+
*,
|
|
59
|
+
items: List[Any],
|
|
60
|
+
max_iterations: int,
|
|
61
|
+
execute_fn: Callable[[Any, int], Awaitable[CapabilityResult]],
|
|
62
|
+
fail_strategy: str = "abort",
|
|
63
|
+
) -> CapabilityResult:
|
|
64
|
+
"""
|
|
65
|
+
执行 LoopStep 语义(确定性)。
|
|
66
|
+
|
|
67
|
+
参数:
|
|
68
|
+
- items:要遍历的集合(必须为 list)
|
|
69
|
+
- max_iterations:最多迭代次数(在 items 长度之上取 min)
|
|
70
|
+
- execute_fn:执行函数,签名为 `(item, index) -> CapabilityResult`
|
|
71
|
+
- fail_strategy:"abort" | "skip" | "collect"
|
|
72
|
+
|
|
73
|
+
返回:
|
|
74
|
+
- CapabilityResult,其中 output 为结果列表;metadata 提供 completed/total/skipped 等摘要。
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# 输入验证:items 必须为 list
|
|
78
|
+
if not isinstance(items, list):
|
|
79
|
+
return CapabilityResult(
|
|
80
|
+
status=CapabilityStatus.FAILED,
|
|
81
|
+
error=f"run_loop: items must be list, got {type(items).__name__}",
|
|
82
|
+
error_code="INVALID_LOOP_INPUT",
|
|
83
|
+
metadata={"actual_type": type(items).__name__},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
results: List[Any] = []
|
|
87
|
+
errors: List[Dict[str, Any]] = []
|
|
88
|
+
effective_max = min(max_iterations, len(items))
|
|
89
|
+
|
|
90
|
+
for idx, item in enumerate(items[:effective_max]):
|
|
91
|
+
self.tick()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
result = await execute_fn(item, idx)
|
|
95
|
+
except Exception as exc:
|
|
96
|
+
result = CapabilityResult(
|
|
97
|
+
status=CapabilityStatus.FAILED,
|
|
98
|
+
error=f"Loop iteration {idx} exception: {exc}",
|
|
99
|
+
error_code="ENGINE_ERROR",
|
|
100
|
+
metadata={"exception_type": type(exc).__name__},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if result.status != CapabilityStatus.SUCCESS:
|
|
104
|
+
# LoopStep 内部一旦出现非 success:
|
|
105
|
+
# - FAILED:按 fail_strategy 处理(abort/skip/collect)
|
|
106
|
+
# - PENDING/RUNNING/CANCELLED:不应被当作成功继续推进(否则会吞掉 needs_approval/incomplete 等语义)
|
|
107
|
+
if result.status in (CapabilityStatus.PENDING, CapabilityStatus.RUNNING, CapabilityStatus.CANCELLED):
|
|
108
|
+
return replace(
|
|
109
|
+
result,
|
|
110
|
+
output=results,
|
|
111
|
+
metadata={
|
|
112
|
+
**dict(getattr(result, "metadata", {}) or {}),
|
|
113
|
+
"completed_iterations": idx,
|
|
114
|
+
"total_planned": effective_max,
|
|
115
|
+
"aborted_status": getattr(result.status, "value", str(result.status)),
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if fail_strategy == "abort":
|
|
120
|
+
return replace(
|
|
121
|
+
result,
|
|
122
|
+
output=results,
|
|
123
|
+
error=f"Loop aborted at iteration {idx}/{effective_max}: {result.error}",
|
|
124
|
+
metadata={
|
|
125
|
+
**dict(getattr(result, "metadata", {}) or {}),
|
|
126
|
+
"completed_iterations": idx,
|
|
127
|
+
"total_planned": effective_max,
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
if fail_strategy == "skip":
|
|
131
|
+
errors.append({"index": idx, "error": result.error})
|
|
132
|
+
continue
|
|
133
|
+
if fail_strategy == "collect":
|
|
134
|
+
results.append({"status": "failed", "error": result.error, "index": idx})
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
results.append(result.output)
|
|
138
|
+
|
|
139
|
+
return CapabilityResult(
|
|
140
|
+
status=CapabilityStatus.SUCCESS,
|
|
141
|
+
output=results,
|
|
142
|
+
metadata={
|
|
143
|
+
"completed_iterations": len(results),
|
|
144
|
+
"total_planned": effective_max,
|
|
145
|
+
"skipped_errors": errors if errors else None,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
__all__ = ["LoopBreakerError", "ExecutionGuards"]
|