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,244 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""执行上下文——跨能力状态传递和调用链管理。"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RecursionLimitError(Exception):
|
|
12
|
+
"""嵌套深度超限。"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CancellationToken:
|
|
16
|
+
"""协作取消 token(不强制打断正在运行的步骤)。"""
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
self._event = asyncio.Event()
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def is_cancelled(self) -> bool:
|
|
25
|
+
"""是否已被标记为取消。"""
|
|
26
|
+
|
|
27
|
+
return self._event.is_set()
|
|
28
|
+
|
|
29
|
+
def cancel(self) -> None:
|
|
30
|
+
"""标记为取消。"""
|
|
31
|
+
|
|
32
|
+
self._event.set()
|
|
33
|
+
|
|
34
|
+
async def wait(self) -> None:
|
|
35
|
+
"""等待取消发生。"""
|
|
36
|
+
|
|
37
|
+
await self._event.wait()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ExecutionContext:
|
|
42
|
+
"""
|
|
43
|
+
执行上下文。
|
|
44
|
+
|
|
45
|
+
参数:
|
|
46
|
+
- run_id: 顶层运行 ID
|
|
47
|
+
- parent_context: 父上下文(用于追溯调用链)
|
|
48
|
+
- depth: 当前嵌套深度(从 0 开始)
|
|
49
|
+
- max_depth: 最大嵌套深度
|
|
50
|
+
- guards: per-run 执行守卫(如全局 loop 熔断计数器)
|
|
51
|
+
- bag: 全局数据袋(浅拷贝传递)
|
|
52
|
+
- step_outputs: 当前层级的步骤输出缓存(step_id → output)
|
|
53
|
+
- call_chain: 调用链记录(能力 ID 列表)
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
run_id: str
|
|
57
|
+
parent_context: Optional[ExecutionContext] = None
|
|
58
|
+
depth: int = 0
|
|
59
|
+
max_depth: int = 10
|
|
60
|
+
guards: Any = None
|
|
61
|
+
cancel_token: Optional[CancellationToken] = None
|
|
62
|
+
bag: MappingProxyType[str, Any] = field(default_factory=lambda: MappingProxyType({}))
|
|
63
|
+
step_outputs: Dict[str, Any] = field(default_factory=dict)
|
|
64
|
+
# step_id -> {status, output, error, report}(面向编排的执行证据;不落盘)
|
|
65
|
+
step_results: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
|
66
|
+
call_chain: List[str] = field(default_factory=list)
|
|
67
|
+
|
|
68
|
+
def __post_init__(self) -> None:
|
|
69
|
+
"""
|
|
70
|
+
运行时强约束:bag 必须为不可变映射(MappingProxyType)。
|
|
71
|
+
|
|
72
|
+
说明:
|
|
73
|
+
- 调用方(包括 adapters/engines/tests)可能仍传入 dict;
|
|
74
|
+
- 这里统一转换为 MappingProxyType(dict(...)),保证 `context.bag[k] = v` 会抛 TypeError。
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
if isinstance(self.bag, MappingProxyType):
|
|
78
|
+
return
|
|
79
|
+
if isinstance(self.bag, Mapping):
|
|
80
|
+
self.bag = MappingProxyType(dict(self.bag))
|
|
81
|
+
return
|
|
82
|
+
self.bag = MappingProxyType({})
|
|
83
|
+
|
|
84
|
+
def with_bag_overlay(self, **updates: Any) -> ExecutionContext:
|
|
85
|
+
"""
|
|
86
|
+
返回带 bag 覆盖的新 ExecutionContext(不修改原对象)。
|
|
87
|
+
|
|
88
|
+
设计目标:
|
|
89
|
+
- 用于 workflow_id/step_id/branch_id 等"临时 hint"注入;
|
|
90
|
+
- 共享 step_outputs/step_results 引用,保证执行证据链可持续累积;
|
|
91
|
+
- bag 为不可变映射,所有修改必须通过 overlay 创建新 context。
|
|
92
|
+
|
|
93
|
+
并发安全警告:
|
|
94
|
+
- 本方法共享 step_outputs 引用(顺序执行设计);
|
|
95
|
+
- 并发分支(如 ParallelStep)必须用 `ExecutionContext(step_outputs=dict(...))` 显式创建副本。
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
base = dict(self.bag)
|
|
99
|
+
base.update(updates)
|
|
100
|
+
return ExecutionContext(
|
|
101
|
+
run_id=self.run_id,
|
|
102
|
+
parent_context=self.parent_context,
|
|
103
|
+
depth=self.depth,
|
|
104
|
+
max_depth=self.max_depth,
|
|
105
|
+
guards=self.guards,
|
|
106
|
+
cancel_token=self.cancel_token,
|
|
107
|
+
bag=MappingProxyType(base),
|
|
108
|
+
step_outputs=self.step_outputs,
|
|
109
|
+
step_results=self.step_results,
|
|
110
|
+
call_chain=self.call_chain,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def with_guards(self, guards: Any) -> ExecutionContext:
|
|
114
|
+
"""
|
|
115
|
+
返回带有新 guards 的 ExecutionContext 副本。
|
|
116
|
+
|
|
117
|
+
设计目标:
|
|
118
|
+
- 确保 per-run guards 隔离,避免计数器跨 run 串扰;
|
|
119
|
+
- 复制可变容器(step_outputs/step_results/call_chain),避免共享引用。
|
|
120
|
+
|
|
121
|
+
参数:
|
|
122
|
+
- guards:新的 ExecutionGuards 实例
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
return ExecutionContext(
|
|
126
|
+
run_id=self.run_id,
|
|
127
|
+
parent_context=self.parent_context,
|
|
128
|
+
depth=self.depth,
|
|
129
|
+
max_depth=self.max_depth,
|
|
130
|
+
guards=guards,
|
|
131
|
+
cancel_token=self.cancel_token,
|
|
132
|
+
bag=self.bag,
|
|
133
|
+
step_outputs=dict(self.step_outputs),
|
|
134
|
+
step_results=dict(self.step_results),
|
|
135
|
+
call_chain=list(self.call_chain),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def child(self, capability_id: str) -> ExecutionContext:
|
|
139
|
+
"""
|
|
140
|
+
创建子上下文。
|
|
141
|
+
|
|
142
|
+
行为:
|
|
143
|
+
- depth + 1;超过 max_depth 抛 RecursionLimitError
|
|
144
|
+
- bag 生成快照(不可变映射视图;修改需通过 with_bag_overlay 返回新 context)
|
|
145
|
+
- step_outputs 清空(子 context 有独立的步骤输出空间)
|
|
146
|
+
- call_chain 追加当前 capability_id
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
new_depth = self.depth + 1
|
|
150
|
+
if new_depth > self.max_depth:
|
|
151
|
+
raise RecursionLimitError(
|
|
152
|
+
f"Recursion depth {new_depth} exceeds max {self.max_depth}. "
|
|
153
|
+
f"Call chain: {self.call_chain + [capability_id]}"
|
|
154
|
+
)
|
|
155
|
+
return ExecutionContext(
|
|
156
|
+
run_id=self.run_id,
|
|
157
|
+
parent_context=self,
|
|
158
|
+
depth=new_depth,
|
|
159
|
+
max_depth=self.max_depth,
|
|
160
|
+
guards=self.guards,
|
|
161
|
+
cancel_token=self.cancel_token,
|
|
162
|
+
bag=MappingProxyType(dict(self.bag)),
|
|
163
|
+
step_outputs={},
|
|
164
|
+
step_results={},
|
|
165
|
+
call_chain=self.call_chain + [capability_id],
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def resolve_mapping(self, expression: str) -> Any:
|
|
169
|
+
"""
|
|
170
|
+
解析映射表达式,从 context 中提取数据。
|
|
171
|
+
|
|
172
|
+
支持:
|
|
173
|
+
- "context.{key}" → self.bag[key]
|
|
174
|
+
- "previous.{key}" → 最后一个 step_output 的 [key]
|
|
175
|
+
- "step.{step_id}.{key}" → self.step_outputs[step_id][key]
|
|
176
|
+
- "step.{step_id}" → self.step_outputs[step_id](整体)
|
|
177
|
+
- "result.{step_id}" → self.step_results[step_id](整体)
|
|
178
|
+
- "result.{step_id}.status" → self.step_results[step_id]["status"]
|
|
179
|
+
- "result.{step_id}.report.status" → self.step_results[step_id]["report"].status(若存在)
|
|
180
|
+
- "literal.{value}" → 字面量字符串
|
|
181
|
+
- "item" → self.bag["__current_item__"]
|
|
182
|
+
- "item.{key}" → self.bag["__current_item__"][key]
|
|
183
|
+
|
|
184
|
+
找不到时返回 None(不抛异常)。
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def _resolve_one(obj: Any, key: str) -> Any:
|
|
188
|
+
"""对 dict/对象做一层 key/attribute 解析;取不到返回 None。"""
|
|
189
|
+
|
|
190
|
+
if obj is None:
|
|
191
|
+
return None
|
|
192
|
+
if isinstance(obj, dict):
|
|
193
|
+
return obj.get(key)
|
|
194
|
+
return getattr(obj, key, None)
|
|
195
|
+
|
|
196
|
+
if expression.startswith("context."):
|
|
197
|
+
key = expression[len("context.") :]
|
|
198
|
+
return self.bag.get(key)
|
|
199
|
+
|
|
200
|
+
if expression.startswith("previous."):
|
|
201
|
+
key = expression[len("previous.") :]
|
|
202
|
+
if not self.step_outputs:
|
|
203
|
+
return None
|
|
204
|
+
last_key = list(self.step_outputs.keys())[-1]
|
|
205
|
+
last_out = self.step_outputs[last_key]
|
|
206
|
+
if isinstance(last_out, dict):
|
|
207
|
+
return last_out.get(key)
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
if expression.startswith("step."):
|
|
211
|
+
rest = expression[len("step.") :]
|
|
212
|
+
parts = rest.split(".", 1)
|
|
213
|
+
step_id = parts[0]
|
|
214
|
+
step_key = parts[1] if len(parts) > 1 else None
|
|
215
|
+
out = self.step_outputs.get(step_id)
|
|
216
|
+
if step_key is None:
|
|
217
|
+
return out
|
|
218
|
+
if isinstance(out, dict):
|
|
219
|
+
return out.get(step_key)
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
if expression.startswith("result."):
|
|
223
|
+
rest = expression[len("result.") :]
|
|
224
|
+
parts = rest.split(".")
|
|
225
|
+
step_id = parts[0]
|
|
226
|
+
cur: Any = self.step_results.get(step_id)
|
|
227
|
+
for key in parts[1:]:
|
|
228
|
+
cur = _resolve_one(cur, key)
|
|
229
|
+
return cur
|
|
230
|
+
|
|
231
|
+
if expression.startswith("literal."):
|
|
232
|
+
return expression[len("literal.") :]
|
|
233
|
+
|
|
234
|
+
if expression == "item":
|
|
235
|
+
return self.bag.get("__current_item__")
|
|
236
|
+
|
|
237
|
+
if expression.startswith("item."):
|
|
238
|
+
key = expression[len("item.") :]
|
|
239
|
+
item = self.bag.get("__current_item__")
|
|
240
|
+
if isinstance(item, dict):
|
|
241
|
+
return item.get(key)
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
return None
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Workflow 元能力声明。"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Dict, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
from .capability import CapabilityRef, CapabilitySpec
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class InputMapping:
|
|
13
|
+
"""
|
|
14
|
+
输入映射——定义步骤输入字段的数据来源。
|
|
15
|
+
|
|
16
|
+
参数:
|
|
17
|
+
- source: 数据源表达式
|
|
18
|
+
- target_field: 目标输入字段名
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
source: str
|
|
22
|
+
target_field: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Step:
|
|
27
|
+
"""
|
|
28
|
+
基础步骤——执行单个能力。
|
|
29
|
+
|
|
30
|
+
参数:
|
|
31
|
+
- id: 步骤 ID(在 Workflow 内唯一)
|
|
32
|
+
- capability: 要调用的能力引用
|
|
33
|
+
- input_mappings: 输入映射列表
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
id: str
|
|
37
|
+
capability: CapabilityRef
|
|
38
|
+
input_mappings: List[InputMapping] = field(default_factory=list)
|
|
39
|
+
timeout_s: Optional[float] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class LoopStep:
|
|
44
|
+
"""
|
|
45
|
+
循环步骤——对集合中每个元素执行能力。
|
|
46
|
+
|
|
47
|
+
参数:
|
|
48
|
+
- id: 步骤 ID
|
|
49
|
+
- capability: 每次循环调用的能力引用
|
|
50
|
+
- iterate_over: 数据源表达式(解析后应为 List)
|
|
51
|
+
- item_input_mappings: 循环内的输入映射(可用 "item"/"item.{key}" 前缀)
|
|
52
|
+
- max_iterations: 单步最大循环次数
|
|
53
|
+
- collect_as: 结果收集字段名
|
|
54
|
+
- fail_strategy: "abort" | "skip" | "collect"
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
id: str
|
|
58
|
+
capability: CapabilityRef
|
|
59
|
+
iterate_over: str
|
|
60
|
+
item_input_mappings: List[InputMapping] = field(default_factory=list)
|
|
61
|
+
max_iterations: int = 100
|
|
62
|
+
collect_as: str = "results"
|
|
63
|
+
fail_strategy: str = "abort"
|
|
64
|
+
timeout_s: Optional[float] = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class ParallelStep:
|
|
69
|
+
"""
|
|
70
|
+
并行步骤——同时执行多个能力。
|
|
71
|
+
|
|
72
|
+
参数:
|
|
73
|
+
- id: 步骤 ID
|
|
74
|
+
- branches: 并行执行的步骤列表
|
|
75
|
+
- join_strategy: "all_success" | "any_success" | "best_effort"
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
id: str
|
|
79
|
+
branches: List["WorkflowStep"] = field(default_factory=list)
|
|
80
|
+
join_strategy: str = "all_success"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class ConditionalStep:
|
|
85
|
+
"""
|
|
86
|
+
条件步骤——根据条件选择执行路径。
|
|
87
|
+
|
|
88
|
+
参数:
|
|
89
|
+
- id: 步骤 ID
|
|
90
|
+
- condition_source: 条件值的数据源表达式
|
|
91
|
+
- branches: 条件值 → 步骤的映射
|
|
92
|
+
- default: 无匹配时的默认步骤
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
id: str
|
|
96
|
+
condition_source: str
|
|
97
|
+
branches: Dict[str, "WorkflowStep"] = field(default_factory=dict)
|
|
98
|
+
default: Optional["WorkflowStep"] = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
WorkflowStep = Union[Step, LoopStep, ParallelStep, ConditionalStep]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class WorkflowSpec:
|
|
106
|
+
"""
|
|
107
|
+
Workflow 声明。
|
|
108
|
+
|
|
109
|
+
参数:
|
|
110
|
+
- base: 公共能力字段
|
|
111
|
+
- steps: 步骤列表(按声明顺序执行,ParallelStep 内部并行)
|
|
112
|
+
- context_schema: 初始 context bag 的 schema(可选)
|
|
113
|
+
- output_mappings: 输出映射(从 context/step_outputs 构造最终输出)
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
base: CapabilitySpec
|
|
117
|
+
steps: List[WorkflowStep] = field(default_factory=list)
|
|
118
|
+
context_schema: Optional[Dict[str, str]] = None
|
|
119
|
+
output_mappings: List[InputMapping] = field(default_factory=list)
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""能力注册表——所有 Spec 的中央存储和查询。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Dict, List, Optional, Set, Union
|
|
5
|
+
|
|
6
|
+
from .manifest import (
|
|
7
|
+
CapabilityDescriptor,
|
|
8
|
+
CapabilityManifestEntry,
|
|
9
|
+
CapabilityVisibility,
|
|
10
|
+
build_manifest_entry_from_spec,
|
|
11
|
+
collect_capability_dependencies,
|
|
12
|
+
validate_manifest_entry_matches_spec,
|
|
13
|
+
)
|
|
14
|
+
from .protocol.agent import AgentSpec
|
|
15
|
+
from .protocol.capability import CapabilityKind, CapabilitySpec
|
|
16
|
+
from .protocol.workflow import ConditionalStep, LoopStep, ParallelStep, Step, WorkflowSpec
|
|
17
|
+
|
|
18
|
+
AnySpec = Union[AgentSpec, WorkflowSpec]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_base(spec: AnySpec) -> CapabilitySpec:
|
|
22
|
+
"""
|
|
23
|
+
从具体 Spec 中提取公共 base。
|
|
24
|
+
|
|
25
|
+
参数:
|
|
26
|
+
- spec:AgentSpec 或 WorkflowSpec
|
|
27
|
+
|
|
28
|
+
返回:
|
|
29
|
+
- CapabilitySpec(公共字段)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
return spec.base
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CapabilityRegistry:
|
|
36
|
+
"""
|
|
37
|
+
能力注册表。
|
|
38
|
+
|
|
39
|
+
线程安全说明:当前为单线程设计(asyncio 单事件循环)。
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self._store: Dict[str, AnySpec] = {}
|
|
44
|
+
self._manifest_store: Dict[str, CapabilityManifestEntry] = {}
|
|
45
|
+
|
|
46
|
+
def register(self, spec: AnySpec) -> None:
|
|
47
|
+
"""
|
|
48
|
+
注册一个能力。
|
|
49
|
+
|
|
50
|
+
参数:
|
|
51
|
+
- spec:能力声明(AgentSpec/WorkflowSpec)
|
|
52
|
+
|
|
53
|
+
说明:
|
|
54
|
+
- 重复注册同一 ID 会覆盖(last-write-wins)。
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
self.register_with_manifest(spec)
|
|
58
|
+
|
|
59
|
+
def register_with_manifest(
|
|
60
|
+
self,
|
|
61
|
+
spec: AnySpec,
|
|
62
|
+
*,
|
|
63
|
+
entry: CapabilityManifestEntry | None = None,
|
|
64
|
+
) -> CapabilityManifestEntry:
|
|
65
|
+
"""
|
|
66
|
+
注册能力并同步维护 manifest entry。
|
|
67
|
+
|
|
68
|
+
参数:
|
|
69
|
+
- spec:能力声明(AgentSpec/WorkflowSpec)
|
|
70
|
+
- entry:可选显式 manifest entry;为空时基于 spec.base 自动生成
|
|
71
|
+
|
|
72
|
+
返回:
|
|
73
|
+
- 实际存储的 manifest entry
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
base = _get_base(spec)
|
|
77
|
+
manifest_entry = entry or build_manifest_entry_from_spec(spec)
|
|
78
|
+
validate_manifest_entry_matches_spec(manifest_entry, spec)
|
|
79
|
+
self._store[base.id] = spec
|
|
80
|
+
self._manifest_store[base.id] = manifest_entry
|
|
81
|
+
return manifest_entry
|
|
82
|
+
|
|
83
|
+
def register_manifest_entry(self, entry: CapabilityManifestEntry) -> CapabilityManifestEntry:
|
|
84
|
+
"""
|
|
85
|
+
仅注册 manifest entry(允许尚未存在 spec)。
|
|
86
|
+
|
|
87
|
+
参数:
|
|
88
|
+
- entry:manifest 元数据
|
|
89
|
+
|
|
90
|
+
返回:
|
|
91
|
+
- 原样返回已存储 entry
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
self._manifest_store[entry.capability_id] = entry
|
|
95
|
+
return entry
|
|
96
|
+
|
|
97
|
+
def get(self, capability_id: str) -> Optional[AnySpec]:
|
|
98
|
+
"""
|
|
99
|
+
查找能力,不存在返回 None。
|
|
100
|
+
|
|
101
|
+
参数:
|
|
102
|
+
- capability_id:能力 ID
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
return self._store.get(capability_id)
|
|
106
|
+
|
|
107
|
+
def get_or_raise(self, capability_id: str) -> AnySpec:
|
|
108
|
+
"""
|
|
109
|
+
查找能力,不存在抛 KeyError。
|
|
110
|
+
|
|
111
|
+
参数:
|
|
112
|
+
- capability_id:能力 ID
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
spec = self.get(capability_id)
|
|
116
|
+
if spec is None:
|
|
117
|
+
raise KeyError(f"Capability not found: {capability_id!r}")
|
|
118
|
+
return spec
|
|
119
|
+
|
|
120
|
+
def list_all(self) -> List[AnySpec]:
|
|
121
|
+
"""列出所有已注册能力。"""
|
|
122
|
+
|
|
123
|
+
return list(self._store.values())
|
|
124
|
+
|
|
125
|
+
def list_by_kind(self, kind: CapabilityKind) -> List[AnySpec]:
|
|
126
|
+
"""
|
|
127
|
+
列出指定种类的所有能力。
|
|
128
|
+
|
|
129
|
+
参数:
|
|
130
|
+
- kind:能力类型
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
return [s for s in self._store.values() if _get_base(s).kind == kind]
|
|
134
|
+
|
|
135
|
+
def list_ids(self) -> List[str]:
|
|
136
|
+
"""列出所有已注册能力的 ID。"""
|
|
137
|
+
|
|
138
|
+
return list(self._store.keys())
|
|
139
|
+
|
|
140
|
+
def get_manifest_entry(self, capability_id: str) -> CapabilityManifestEntry | None:
|
|
141
|
+
"""
|
|
142
|
+
查找 manifest entry,不存在返回 None。
|
|
143
|
+
|
|
144
|
+
参数:
|
|
145
|
+
- capability_id:能力 ID
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
entry = self._manifest_store.get(capability_id)
|
|
149
|
+
if entry is not None:
|
|
150
|
+
return entry
|
|
151
|
+
spec = self._store.get(capability_id)
|
|
152
|
+
if spec is None:
|
|
153
|
+
return None
|
|
154
|
+
entry = build_manifest_entry_from_spec(spec)
|
|
155
|
+
self._manifest_store[capability_id] = entry
|
|
156
|
+
return entry
|
|
157
|
+
|
|
158
|
+
def get_descriptor(self, capability_id: str) -> CapabilityDescriptor | None:
|
|
159
|
+
"""
|
|
160
|
+
查询 capability descriptor。
|
|
161
|
+
|
|
162
|
+
参数:
|
|
163
|
+
- capability_id:能力 ID
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
entry = self.get_manifest_entry(capability_id)
|
|
167
|
+
if entry is None:
|
|
168
|
+
return None
|
|
169
|
+
spec = self._store.get(capability_id)
|
|
170
|
+
return CapabilityDescriptor(
|
|
171
|
+
entry=entry,
|
|
172
|
+
spec=spec,
|
|
173
|
+
dependencies=collect_capability_dependencies(spec),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def list_descriptors(
|
|
177
|
+
self,
|
|
178
|
+
*,
|
|
179
|
+
visibility: CapabilityVisibility | None = None,
|
|
180
|
+
exposed_only: bool = False,
|
|
181
|
+
) -> list[CapabilityDescriptor]:
|
|
182
|
+
"""
|
|
183
|
+
列出 capability descriptors。
|
|
184
|
+
|
|
185
|
+
参数:
|
|
186
|
+
- visibility:可选可见性过滤
|
|
187
|
+
- exposed_only:仅返回 `entry.expose=True` 的能力
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
ids: list[str] = list(self._manifest_store.keys())
|
|
191
|
+
for capability_id in self._store.keys():
|
|
192
|
+
if capability_id not in self._manifest_store:
|
|
193
|
+
ids.append(capability_id)
|
|
194
|
+
|
|
195
|
+
descriptors: list[CapabilityDescriptor] = []
|
|
196
|
+
for capability_id in ids:
|
|
197
|
+
descriptor = self.get_descriptor(capability_id)
|
|
198
|
+
if descriptor is None:
|
|
199
|
+
continue
|
|
200
|
+
if visibility is not None and descriptor.entry.visibility != visibility:
|
|
201
|
+
continue
|
|
202
|
+
if exposed_only and not descriptor.entry.expose:
|
|
203
|
+
continue
|
|
204
|
+
descriptors.append(descriptor)
|
|
205
|
+
return descriptors
|
|
206
|
+
|
|
207
|
+
def has(self, capability_id: str) -> bool:
|
|
208
|
+
"""
|
|
209
|
+
检查能力是否已注册。
|
|
210
|
+
|
|
211
|
+
参数:
|
|
212
|
+
- capability_id:能力 ID
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
return capability_id in self._store
|
|
216
|
+
|
|
217
|
+
def unregister(self, capability_id: str) -> bool:
|
|
218
|
+
"""
|
|
219
|
+
注销能力。
|
|
220
|
+
|
|
221
|
+
参数:
|
|
222
|
+
- capability_id:能力 ID
|
|
223
|
+
|
|
224
|
+
返回:
|
|
225
|
+
- True 表示存在并已删除;False 表示不存在
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
if capability_id in self._store:
|
|
229
|
+
del self._store[capability_id]
|
|
230
|
+
removed = True
|
|
231
|
+
else:
|
|
232
|
+
removed = False
|
|
233
|
+
if capability_id in self._manifest_store:
|
|
234
|
+
del self._manifest_store[capability_id]
|
|
235
|
+
return True
|
|
236
|
+
return removed
|
|
237
|
+
|
|
238
|
+
def validate_dependencies(self) -> List[str]:
|
|
239
|
+
"""
|
|
240
|
+
校验所有能力的依赖是否已注册。
|
|
241
|
+
|
|
242
|
+
检查范围:
|
|
243
|
+
- AgentSpec.collaborators / callable_workflows 中引用的能力 ID
|
|
244
|
+
- WorkflowSpec 中所有 Step/LoopStep 的 capability.id
|
|
245
|
+
- ParallelStep.branches 内的步骤
|
|
246
|
+
- ConditionalStep.branches/default 内的步骤
|
|
247
|
+
|
|
248
|
+
返回:
|
|
249
|
+
- 缺失的 ID 列表(空列表表示全部满足)
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
missing: Set[str] = set()
|
|
253
|
+
|
|
254
|
+
for spec in self._store.values():
|
|
255
|
+
if isinstance(spec, AgentSpec):
|
|
256
|
+
for ref in spec.collaborators:
|
|
257
|
+
if ref.id not in self._store:
|
|
258
|
+
missing.add(ref.id)
|
|
259
|
+
for ref in spec.callable_workflows:
|
|
260
|
+
if ref.id not in self._store:
|
|
261
|
+
missing.add(ref.id)
|
|
262
|
+
|
|
263
|
+
elif isinstance(spec, WorkflowSpec):
|
|
264
|
+
self._collect_step_deps(spec.steps, missing)
|
|
265
|
+
|
|
266
|
+
return sorted(missing)
|
|
267
|
+
|
|
268
|
+
def _collect_step_deps(self, steps: list, missing: Set[str]) -> None:
|
|
269
|
+
"""
|
|
270
|
+
递归收集步骤中的能力依赖。
|
|
271
|
+
|
|
272
|
+
参数:
|
|
273
|
+
- steps:步骤列表(可能包含嵌套步骤)
|
|
274
|
+
- missing:缺失依赖的能力 ID(原地写入)
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
for step in steps:
|
|
278
|
+
if isinstance(step, (Step, LoopStep)):
|
|
279
|
+
if step.capability.id not in self._store:
|
|
280
|
+
missing.add(step.capability.id)
|
|
281
|
+
elif isinstance(step, ParallelStep):
|
|
282
|
+
# WorkflowAdapter 支持在 branches 中嵌套任意 WorkflowStep,因此依赖校验必须递归覆盖。
|
|
283
|
+
self._collect_step_deps(list(step.branches), missing)
|
|
284
|
+
elif isinstance(step, ConditionalStep):
|
|
285
|
+
self._collect_step_deps(list(step.branches.values()), missing)
|
|
286
|
+
if step.default is not None:
|
|
287
|
+
self._collect_step_deps([step.default], missing)
|