@springbrand/agent-runtime 0.1.3-alpha.0 → 0.1.3-alpha.1
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.
- package/package.json +1 -1
- package/src/index.ts +9 -11
- package/src/kernel/bindings.ts +26 -3
- package/src/kernel/extensions.ts +1 -1
- package/src/kernel/profile.ts +3 -4
- package/src/layers/orchestration/temporary-agent/runner.ts +0 -65
- package/src/pi/assembly/context.ts +1 -3
- package/src/pi/assembly/snapshot.ts +12 -7
- package/src/pi/runtime-adapter/assembly.ts +16 -66
- package/src/pi/runtime-adapter/execution.ts +18 -6
- package/src/pi/tool/compiler.ts +5 -32
- package/src/pi/tool/core-host.ts +10 -7
- package/src/pi/tool/core.ts +7 -4
- package/src/pi/tool/mcp.ts +2 -2
- package/src/pi/tool/schedule.ts +46 -2
- package/src/pi/tool/skill.ts +1 -1
- package/src/plugins.ts +311 -521
- package/src/runtime.ts +5 -4
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -23,11 +23,9 @@
|
|
|
23
23
|
* - `canonical transcript` 是 Pi 持久化和恢复 Turn 时使用的权威消息历史。
|
|
24
24
|
* - `admission` 是提示进入 Turn 前的接纳、去重和状态登记流程。
|
|
25
25
|
* - `AgentConfig` 是应用交给 Runtime 的一次完整装配说明。
|
|
26
|
-
* - `AgentPlugin`
|
|
27
|
-
* - `PluginKind`
|
|
28
|
-
* - `
|
|
29
|
-
* - `prepare` 执行 `loader`,但不会改动正在运行的 Runtime。
|
|
30
|
-
* - `RuntimeContributionContext` 是 Plugin 写入候选配置的受限接口。
|
|
26
|
+
* - `AgentPlugin` 是一种能力的声明式准备单元。
|
|
27
|
+
* - `PluginKind` 同时收窄 Plugin 可以准备的字段,并作为内部稳定合并顺序。
|
|
28
|
+
* - `prepare` 读取业务数据或外部绑定,但不会改动正在运行的 Runtime。
|
|
31
29
|
* - `Port` 是 Runtime 调用外部能力时依赖的最小接口。
|
|
32
30
|
* - `RuntimeBindings` 是本次装配选中的 Port 和可执行对象集合。
|
|
33
31
|
* - `RuntimeBuilder` 是包内实现,用来校验贡献并生成候选结果。
|
|
@@ -40,8 +38,7 @@
|
|
|
40
38
|
*
|
|
41
39
|
* Snapshot 不允许替换已选集合,但不承诺把每个 Port 的内部对象深冻结。
|
|
42
40
|
*
|
|
43
|
-
* 正常调用顺序是 `
|
|
44
|
-
* 待确认:当前实现先校验 Plugin kind,再在 `prepare` 中调用 `loader`;这里的阶段简写是否仍应作为对外文档保留,需要与实际命名统一。
|
|
41
|
+
* 正常调用顺序是 `prepare → merge → validate → freeze → guard → commit`。
|
|
45
42
|
*
|
|
46
43
|
* 应用只创建 `AgentConfig` 和 Plugin。
|
|
47
44
|
*
|
|
@@ -62,11 +59,13 @@ export type {
|
|
|
62
59
|
AgentPlugin,
|
|
63
60
|
AgentPluginSpec,
|
|
64
61
|
PluginKind,
|
|
65
|
-
|
|
62
|
+
PluginPreparation,
|
|
66
63
|
PreparedPlugin,
|
|
67
|
-
RuntimeContributionContext,
|
|
68
64
|
RuntimeDegradation,
|
|
65
|
+
RuntimeExtensionContribution,
|
|
69
66
|
RuntimeProfileContribution,
|
|
67
|
+
RuntimeSkillContribution,
|
|
68
|
+
RuntimeToolSurfacePolicy,
|
|
70
69
|
} from "./plugins";
|
|
71
70
|
export type { ModelOption } from "./lib/model-catalog";
|
|
72
71
|
export {
|
|
@@ -105,7 +104,7 @@ export {
|
|
|
105
104
|
browserQuickActionPiToolCandidates,
|
|
106
105
|
} from "./pi/tool";
|
|
107
106
|
export {
|
|
108
|
-
|
|
107
|
+
createWorkspaceCodeExecutionPort,
|
|
109
108
|
} from "./pi/tool";
|
|
110
109
|
export {
|
|
111
110
|
assemblePiExtensions,
|
|
@@ -119,7 +118,6 @@ export type {
|
|
|
119
118
|
TemporaryAgentRunContext,
|
|
120
119
|
} from "./layers/orchestration/temporary-agent/core";
|
|
121
120
|
export {
|
|
122
|
-
bridgeTemporaryAgentToolApprovals,
|
|
123
121
|
temporaryAgentExtensionIsSafe,
|
|
124
122
|
temporaryAgentToolAllowed,
|
|
125
123
|
} from "./layers/orchestration/temporary-agent/runner";
|
package/src/kernel/bindings.ts
CHANGED
|
@@ -186,6 +186,17 @@ export interface WorkspacePort {
|
|
|
186
186
|
glob(pattern: string): Promise<WorkspaceFileInfo[]>;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* 向 Runtime 提供已组装的 Workspace Code Mode 执行能力。
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* Host Adapter 负责绑定 Durable Object、Worker Loader、网络出口和
|
|
194
|
+
* Workspace;Tool Surface 只把这个已授权 Port 转成 `execute` Tool。
|
|
195
|
+
*/
|
|
196
|
+
export interface RuntimeCodeExecutionPort {
|
|
197
|
+
execute(input: { code: string }): Promise<unknown>;
|
|
198
|
+
}
|
|
199
|
+
|
|
189
200
|
/**
|
|
190
201
|
* 汇总一个已限定作用域的 Workspace 用量。
|
|
191
202
|
*
|
|
@@ -540,6 +551,14 @@ export interface RuntimeScheduleUpdate {
|
|
|
540
551
|
tz?: string;
|
|
541
552
|
}
|
|
542
553
|
|
|
554
|
+
/** 定时任务可切换到的已授权 User Agent 摘要。 */
|
|
555
|
+
export interface RuntimeScheduleAgentSummary {
|
|
556
|
+
id: string;
|
|
557
|
+
name: string;
|
|
558
|
+
description: string | null;
|
|
559
|
+
isDefault: boolean;
|
|
560
|
+
}
|
|
561
|
+
|
|
543
562
|
/**
|
|
544
563
|
* 让 Runtime 通过 Host 管理当前用户的定时任务。
|
|
545
564
|
*
|
|
@@ -573,6 +592,10 @@ export interface RuntimeSchedulePort {
|
|
|
573
592
|
): Promise<{ ok: boolean }>;
|
|
574
593
|
pause(id: string): Promise<{ ok: boolean }>;
|
|
575
594
|
resume(id: string): Promise<{ ok: boolean }>;
|
|
595
|
+
/** 列出当前用户可用于执行定时任务的 User Agent。 */
|
|
596
|
+
listAgents(): Promise<RuntimeScheduleAgentSummary[]>;
|
|
597
|
+
/** 更换一条定时任务的执行 Agent。 */
|
|
598
|
+
changeAgent(id: string, userAgentId: string): Promise<{ ok: boolean }>;
|
|
576
599
|
/**
|
|
577
600
|
* 按 ID 取消一条定时任务。
|
|
578
601
|
*
|
|
@@ -786,7 +809,7 @@ export interface RuntimeModelEndpoint {
|
|
|
786
809
|
* 向 Runtime 提供本次装配可用的模型端点。
|
|
787
810
|
*
|
|
788
811
|
* @remarks
|
|
789
|
-
* Provider Plugin
|
|
812
|
+
* Provider Plugin 准备它,Builder 在提交前校验,Pi Adapter 在激活与每次解析模型时读取。
|
|
790
813
|
*
|
|
791
814
|
* Runtime 只依赖已解析结果,不知道环境变量或业务模型配置的来源。
|
|
792
815
|
*/
|
|
@@ -800,7 +823,7 @@ export interface RuntimeProviderPort {
|
|
|
800
823
|
* 向 Runtime 提供 Cloudflare 执行平台上的可授权能力。
|
|
801
824
|
*
|
|
802
825
|
* @remarks
|
|
803
|
-
* Platform Plugin
|
|
826
|
+
* Platform Plugin 准备它,Workspace Codemode、Browser 工具、遥测和工具门卫按需使用。
|
|
804
827
|
*
|
|
805
828
|
* Worker Loader 与网络出口由 Host 选择,使 Dynamic Worker 只获得已授权绑定;术语见 `../index.ts`。
|
|
806
829
|
*/
|
|
@@ -853,7 +876,7 @@ export interface RuntimeSkillBindings {
|
|
|
853
876
|
* 限定一个 Skill 脚本可使用的网络、Workspace 和工具。
|
|
854
877
|
*
|
|
855
878
|
* @remarks
|
|
856
|
-
* Skill Plugin
|
|
879
|
+
* Skill Plugin 在准备来源时设置,Pi Skill 工具在决定是否注入脚本执行能力时读取。
|
|
857
880
|
*
|
|
858
881
|
* 这是已授权结果,执行期不应为某个 Skill 自动放宽。
|
|
859
882
|
*/
|
package/src/kernel/extensions.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface RuntimeExtensionContextDefinition {
|
|
|
43
43
|
* 把 Extension 的 manifest 与待加载源码放在同一份配置里。
|
|
44
44
|
*
|
|
45
45
|
* @remarks
|
|
46
|
-
* extension Plugin
|
|
46
|
+
* extension Plugin 在准备结果中提供它,Pi Extension 适配器在候选 Runtime 装配时加载。
|
|
47
47
|
*
|
|
48
48
|
* manifest 与源码一起传递,可避免授权信息与实际执行代码在不同查找步骤中错位。
|
|
49
49
|
*
|
package/src/kernel/profile.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type ThinkingEffort =
|
|
|
22
22
|
* 保存一次装配选定的内存开关与上下文预算。
|
|
23
23
|
*
|
|
24
24
|
* @remarks
|
|
25
|
-
* memory Plugin
|
|
25
|
+
* memory Plugin 在准备结果中提供它,Runtime 在构建上下文时读取。
|
|
26
26
|
*
|
|
27
27
|
* 预算与开关放在同一个 Profile 中,确保一次 Snapshot 只使用一套内存配置。
|
|
28
28
|
*
|
|
@@ -32,14 +32,13 @@ export interface RuntimeMemoryProfile {
|
|
|
32
32
|
enabled: boolean;
|
|
33
33
|
memoryTokens: number;
|
|
34
34
|
preferencesTokens: number;
|
|
35
|
-
compactAfterTokens: number;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
38
|
* 指明本次装配允许连接的一个 MCP 服务。
|
|
40
39
|
*
|
|
41
40
|
* @remarks
|
|
42
|
-
* connector Plugin
|
|
41
|
+
* connector Plugin 在准备结果中加入它,Pi 工具装配只选择 Host 已就绪且 URL 匹配的连接。
|
|
43
42
|
*
|
|
44
43
|
* 这里只保留名称和 URL;连接生命周期、凭据与重试仍由 Host 管理。
|
|
45
44
|
*
|
|
@@ -71,7 +70,7 @@ export interface RuntimeDenyPolicy {
|
|
|
71
70
|
* 保存一次装配中真正会影响执行的参数。
|
|
72
71
|
*
|
|
73
72
|
* @remarks
|
|
74
|
-
* `RuntimeBuilder` 在所有 Plugin
|
|
73
|
+
* `RuntimeBuilder` 在所有 Plugin 声明合并完成后生成它,Kernel 只通过已提交的 Snapshot 读取。
|
|
75
74
|
*
|
|
76
75
|
* 它不包含业务身份、归属键或存储地址,因为这些选择属于 Host,不应被冻结成执行参数。
|
|
77
76
|
*
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { RuntimeExtensionConfig } from "../../../kernel/extensions";
|
|
2
|
-
import {
|
|
3
|
-
requiresExecutionApproval,
|
|
4
|
-
type ExecutionLevel,
|
|
5
|
-
} from "../../../lib/execution-level";
|
|
6
|
-
import type { PiToolCandidate } from "../../../pi/tool/compiler";
|
|
7
2
|
|
|
8
3
|
const BLOCKED_TOOLS = new Set([
|
|
9
4
|
"dispatch_background",
|
|
@@ -46,66 +41,6 @@ export function temporaryAgentToolAllowed(
|
|
|
46
41
|
);
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
/**
|
|
50
|
-
* 给临时 Agent 里需要审批的 Tool 包上父 Session 审批。
|
|
51
|
-
*
|
|
52
|
-
* @remarks
|
|
53
|
-
* 宿主在筛完 Tool 候选项后调用;返回的候选项可直接交给临时 Agent 的 Pi Tool 编译流程。
|
|
54
|
-
*
|
|
55
|
-
* 包装层先按父 Agent 的执行档位请求批准,然后才调原 `execute`;审批完成后把包装候选降为 `safe`,避免临时 Agent 重复审批。
|
|
56
|
-
*
|
|
57
|
-
* Runtime、Session 和 Tool 的术语见 `src/index.ts`。
|
|
58
|
-
*/
|
|
59
|
-
export function bridgeTemporaryAgentToolApprovals(
|
|
60
|
-
candidates: readonly PiToolCandidate[],
|
|
61
|
-
executionLevel: ExecutionLevel,
|
|
62
|
-
requestApproval: (request: {
|
|
63
|
-
toolName: string;
|
|
64
|
-
executionLevel: ExecutionLevel;
|
|
65
|
-
requiredExecutionLevel: ExecutionLevel;
|
|
66
|
-
input: unknown;
|
|
67
|
-
toolCallId: string;
|
|
68
|
-
signal: AbortSignal;
|
|
69
|
-
}) => Promise<void>,
|
|
70
|
-
): PiToolCandidate[] {
|
|
71
|
-
return candidates.map((candidate) => {
|
|
72
|
-
if (!requiresExecutionApproval(
|
|
73
|
-
executionLevel,
|
|
74
|
-
candidate.requiredExecutionLevel,
|
|
75
|
-
)) {
|
|
76
|
-
return candidate;
|
|
77
|
-
}
|
|
78
|
-
const execute = candidate.tool.execute;
|
|
79
|
-
return {
|
|
80
|
-
...candidate,
|
|
81
|
-
requiredExecutionLevel: "safe",
|
|
82
|
-
tool: {
|
|
83
|
-
...candidate.tool,
|
|
84
|
-
// 作用:在执行原 Tool 前向父 Session 请求审批。
|
|
85
|
-
// 调用:Pi Tool 执行器调用,参数和进度回调原样传给原 Tool。
|
|
86
|
-
// 原因:某些调用方不传 signal,但审批契约需要一个 `AbortSignal`,因此只在缺失时创建一个不会自行取消的信号。
|
|
87
|
-
async execute(toolCallId, input, signal, onUpdate) {
|
|
88
|
-
const activeSignal = signal ?? new AbortController().signal;
|
|
89
|
-
await requestApproval({
|
|
90
|
-
toolName: candidate.tool.name,
|
|
91
|
-
executionLevel,
|
|
92
|
-
requiredExecutionLevel: candidate.requiredExecutionLevel,
|
|
93
|
-
input,
|
|
94
|
-
toolCallId,
|
|
95
|
-
signal: activeSignal,
|
|
96
|
-
});
|
|
97
|
-
return execute(
|
|
98
|
-
toolCallId,
|
|
99
|
-
input,
|
|
100
|
-
activeSignal,
|
|
101
|
-
onUpdate,
|
|
102
|
-
);
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
44
|
/**
|
|
110
45
|
* 判断一个 Extension 是否安全到可以交给临时 Agent。
|
|
111
46
|
*
|
|
@@ -116,7 +116,7 @@ export async function compactPiContext(input: Readonly<{
|
|
|
116
116
|
const settings = DEFAULT_COMPACTION_SETTINGS;
|
|
117
117
|
if (
|
|
118
118
|
!settings.enabled ||
|
|
119
|
-
estimateContextTokens(currentMessages).tokens
|
|
119
|
+
estimateContextTokens(currentMessages).tokens <
|
|
120
120
|
input.compactAfterTokens
|
|
121
121
|
) {
|
|
122
122
|
return {
|
|
@@ -479,7 +479,6 @@ export async function assemblePiSystemContext(input: Readonly<{
|
|
|
479
479
|
) => Promise<string | null>;
|
|
480
480
|
}>): Promise<Readonly<{
|
|
481
481
|
systemPrompt: string;
|
|
482
|
-
compactAfterTokens: number;
|
|
483
482
|
degradations: readonly RuntimeDegradation[];
|
|
484
483
|
}>> {
|
|
485
484
|
const [memory, extensions] = await Promise.all([
|
|
@@ -499,7 +498,6 @@ export async function assemblePiSystemContext(input: Readonly<{
|
|
|
499
498
|
|
|
500
499
|
return Object.freeze({
|
|
501
500
|
systemPrompt: parts.join("\n\n"),
|
|
502
|
-
compactAfterTokens: input.memory.compactAfterTokens,
|
|
503
501
|
degradations: Object.freeze([
|
|
504
502
|
...memory.degradations,
|
|
505
503
|
...extensions.degradations,
|
|
@@ -16,13 +16,22 @@ import { assembleSystemPrompt } from "../../lib/prompt";
|
|
|
16
16
|
import { resolvePiModel } from "../runtime-adapter/models";
|
|
17
17
|
import type { PiToolCandidate } from "../tool/compiler";
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 收集完动态 Extension/MCP 候选后,一次性生成最终 Tool Surface。
|
|
21
|
+
*/
|
|
22
|
+
export interface PiToolSurface {
|
|
23
|
+
finalize(
|
|
24
|
+
authorizedCandidates: readonly PiToolCandidate[],
|
|
25
|
+
): readonly PiToolCandidate[];
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
/** Immutable inputs consumed directly by Pi Agent Core. */
|
|
20
29
|
export interface PiRuntimeAssembly {
|
|
21
30
|
readonly model: Model<Api>;
|
|
22
31
|
readonly thinkingLevel: ThinkingLevel;
|
|
23
32
|
readonly systemPrompt: string;
|
|
24
33
|
readonly messages: readonly AgentMessage[];
|
|
25
|
-
readonly
|
|
34
|
+
readonly toolSurface: PiToolSurface;
|
|
26
35
|
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
27
36
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
28
37
|
}
|
|
@@ -34,7 +43,7 @@ interface PiRuntimeAssemblyInput {
|
|
|
34
43
|
>;
|
|
35
44
|
readonly provider: RuntimeProviderPort;
|
|
36
45
|
readonly messages?: readonly AgentMessage[];
|
|
37
|
-
readonly
|
|
46
|
+
readonly toolSurface: PiToolSurface;
|
|
38
47
|
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
39
48
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
40
49
|
}
|
|
@@ -172,15 +181,11 @@ export function createPiRuntimeAssembly(
|
|
|
172
181
|
input: PiRuntimeAssemblyInput,
|
|
173
182
|
): PiRuntimeAssembly {
|
|
174
183
|
const messages = [...(input.messages ?? [])];
|
|
175
|
-
const toolCandidates = input.toolCandidates.map(
|
|
176
|
-
(candidate) => Object.freeze({ ...candidate }),
|
|
177
|
-
);
|
|
178
184
|
const mcpServers = input.mcpServers.map(
|
|
179
185
|
(server) => Object.freeze({ ...server }),
|
|
180
186
|
);
|
|
181
187
|
const extensions = input.extensions.map(freezeExtension);
|
|
182
188
|
Object.freeze(messages);
|
|
183
|
-
Object.freeze(toolCandidates);
|
|
184
189
|
Object.freeze(mcpServers);
|
|
185
190
|
Object.freeze(extensions);
|
|
186
191
|
|
|
@@ -193,7 +198,7 @@ export function createPiRuntimeAssembly(
|
|
|
193
198
|
input.profile.systemPrompt,
|
|
194
199
|
),
|
|
195
200
|
messages,
|
|
196
|
-
|
|
201
|
+
toolSurface: input.toolSurface,
|
|
197
202
|
mcpServers,
|
|
198
203
|
extensions,
|
|
199
204
|
});
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
type PiLoadedExtension,
|
|
20
20
|
} from "../assembly";
|
|
21
21
|
import {
|
|
22
|
-
compilePiTools,
|
|
23
22
|
createPiMcpToolCandidates,
|
|
24
23
|
listExtensionsPiToolCandidate,
|
|
25
24
|
type PiMcpHost,
|
|
@@ -41,7 +40,7 @@ import {
|
|
|
41
40
|
interface PiAssemblySnapshot {
|
|
42
41
|
readonly pi: Pick<
|
|
43
42
|
PiRuntimeAssembly,
|
|
44
|
-
"extensions" | "
|
|
43
|
+
"extensions" | "toolSurface"
|
|
45
44
|
>;
|
|
46
45
|
readonly profile: Pick<
|
|
47
46
|
RuntimeProfile,
|
|
@@ -75,10 +74,7 @@ interface PreparedPiAssembly {
|
|
|
75
74
|
readonly loaded: readonly PiLoadedExtension[];
|
|
76
75
|
readonly context: readonly PiExtensionContextContribution[];
|
|
77
76
|
readonly degradations: PiExtensionAssembly["degradations"];
|
|
78
|
-
|
|
79
|
-
// preparePiAssembly 用它预检,preparePiRuntime 再读取并冻结最终列表。
|
|
80
|
-
// MCP 就绪状态由 Host 提供,因此这里不能只拼接初始化时的静态数组。
|
|
81
|
-
toolCandidates(): PiToolCandidate[];
|
|
77
|
+
readonly candidates: readonly PiToolCandidate[];
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
/**
|
|
@@ -391,54 +387,7 @@ export function readPinnedPiRuntime(
|
|
|
391
387
|
|
|
392
388
|
// #region 扩展与工具装配
|
|
393
389
|
|
|
394
|
-
|
|
395
|
-
// 把准备快照、已加载扩展和 MCP Host 放在一个装配对象里。
|
|
396
|
-
// preparePiAssembly 在扩展加载完成后构造它。
|
|
397
|
-
// 这里保留源对象而不复制列表,让每个访问器都读取同一份结果。
|
|
398
|
-
constructor(
|
|
399
|
-
private readonly snapshot: PiAssemblySnapshot,
|
|
400
|
-
private readonly extensions: PiExtensionAssembly,
|
|
401
|
-
private readonly mcpHost: PiMcpHost,
|
|
402
|
-
) {}
|
|
403
|
-
|
|
404
|
-
// 返回成功加载的扩展。
|
|
405
|
-
// preparePiRuntime 在生成版本描述时读取这个列表。
|
|
406
|
-
// 直接代理扩展装配,避免维护可能漂移的第二份列表。
|
|
407
|
-
get loaded(): readonly PiLoadedExtension[] {
|
|
408
|
-
return this.extensions.loaded;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// 返回已加载扩展声明的上下文贡献。
|
|
412
|
-
// pinPiRuntime 在提交准入时把它们交给系统上下文装配器。
|
|
413
|
-
// 上下文值在固定阶段才读取,所以不能提前写死在准备阶段的提示词中。
|
|
414
|
-
get context(): readonly PiExtensionContextContribution[] {
|
|
415
|
-
return this.extensions.context;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// 返回准备阶段被降级处理、而不是继续抛出的扩展失败。
|
|
419
|
-
// preparePiRuntime 把它们与快照降级合并,用于报告 Runtime 加载结果。
|
|
420
|
-
// 直接代理可保留扩展装配器生成的原始降级记录。
|
|
421
|
-
get degradations(): PiExtensionAssembly["degradations"] {
|
|
422
|
-
return this.extensions.degradations;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// 汇总内置、扩展列表、扩展和当前已就绪的 MCP 工具。
|
|
426
|
-
// 准备流程先调用它做校验,再冻结一份列表供后续 Turn 使用。
|
|
427
|
-
// 每次重新生成是因为 MCP 候选来自 Host 当前的连接状态。
|
|
428
|
-
toolCandidates(): PiToolCandidate[] {
|
|
429
|
-
return [
|
|
430
|
-
...this.snapshot.pi.toolCandidates,
|
|
431
|
-
...this.extensions.candidates,
|
|
432
|
-
listExtensionsPiToolCandidate(this.extensions.loaded),
|
|
433
|
-
...createPiMcpToolCandidates(
|
|
434
|
-
this.mcpHost,
|
|
435
|
-
this.snapshot.profile.mcpServers,
|
|
436
|
-
),
|
|
437
|
-
];
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// 加载已启用扩展,并确认汇总后的工具可以正常编译。
|
|
390
|
+
// 加载已启用扩展,并把 Extension/MCP 候选交给唯一 Tool Surface 最终化。
|
|
442
391
|
// preparePiRuntime 为每份待配置的 Runtime 快照调用它一次。
|
|
443
392
|
// 工作区能力在共享加载边界校验,单个扩展加载失败则保留为降级结果。
|
|
444
393
|
async function preparePiAssembly(
|
|
@@ -469,18 +418,19 @@ async function preparePiAssembly(
|
|
|
469
418
|
});
|
|
470
419
|
},
|
|
471
420
|
});
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
extensions,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
421
|
+
return Object.freeze({
|
|
422
|
+
loaded: extensions.loaded,
|
|
423
|
+
context: extensions.context,
|
|
424
|
+
degradations: extensions.degradations,
|
|
425
|
+
candidates: snapshot.pi.toolSurface.finalize([
|
|
426
|
+
...extensions.candidates,
|
|
427
|
+
listExtensionsPiToolCandidate(extensions.loaded),
|
|
428
|
+
...createPiMcpToolCandidates(
|
|
429
|
+
options.mcpHost,
|
|
430
|
+
snapshot.profile.mcpServers,
|
|
431
|
+
),
|
|
432
|
+
]),
|
|
482
433
|
});
|
|
483
|
-
return prepared;
|
|
484
434
|
}
|
|
485
435
|
|
|
486
436
|
// #endregion
|
|
@@ -500,7 +450,7 @@ export async function preparePiRuntime(
|
|
|
500
450
|
owner: object,
|
|
501
451
|
): Promise<PreparedPiRuntime> {
|
|
502
452
|
const assembly = await preparePiAssembly(options);
|
|
503
|
-
const candidates = assembly.
|
|
453
|
+
const candidates = assembly.candidates;
|
|
504
454
|
return Object.freeze(new PreparedRuntime(
|
|
505
455
|
describeRuntime(options.snapshot, candidates, assembly.loaded),
|
|
506
456
|
Object.freeze([
|
|
@@ -34,6 +34,8 @@ import { resolvePiApiKey, withProviderRetry } from "./models";
|
|
|
34
34
|
|
|
35
35
|
// #region Single-run Pi bridge
|
|
36
36
|
|
|
37
|
+
const MAX_MODEL_TURNS = 30;
|
|
38
|
+
|
|
37
39
|
/**
|
|
38
40
|
* 把 Pi 的终止原因翻译成本仓的回合结局。
|
|
39
41
|
*
|
|
@@ -81,7 +83,6 @@ interface PiTurnAdapterOptions {
|
|
|
81
83
|
// PiTurnAdapter.run 在创建 PiCore 前调用它,宿主应返回当时最新的 transcript。
|
|
82
84
|
// 这里保留为延迟读取,是为了避免适配器创建后新增的恢复消息被旧快照漏掉。
|
|
83
85
|
readonly canonicalMessages: () => Promise<readonly AgentMessage[]>;
|
|
84
|
-
readonly denyTools?: readonly string[];
|
|
85
86
|
// 把工具的最终结果交回宿主持久化。
|
|
86
87
|
// 共享工具编译器在向 Pi 返回成功或规范化失败前调用它。
|
|
87
88
|
// 这个先落库再发布结果的顺序是恢复边界,不能改成事后异步补写。
|
|
@@ -113,10 +114,9 @@ class PiTurnAdapter {
|
|
|
113
114
|
|
|
114
115
|
// 把 Tool candidate 变成 Pi 可以执行的受治理 Tool。
|
|
115
116
|
// 正常运行会编译 Pinned Runtime 的整份列表,恢复时只编译指定 Tool candidate。
|
|
116
|
-
//
|
|
117
|
+
// 输出限额和持久化结算统一走共享编译器,避免恢复另开旁路。
|
|
117
118
|
private compile(candidates: readonly PiToolCandidate[]) {
|
|
118
119
|
return compilePiTools(candidates, {
|
|
119
|
-
deny: this.opts.denyTools,
|
|
120
120
|
governance: this.governance,
|
|
121
121
|
settle: this.opts.settle,
|
|
122
122
|
});
|
|
@@ -162,11 +162,13 @@ class PiTurnAdapter {
|
|
|
162
162
|
readonly signal?: AbortSignal;
|
|
163
163
|
},
|
|
164
164
|
listener: (event: AgentEvent) => void | Promise<void>,
|
|
165
|
-
): Promise<
|
|
165
|
+
): Promise<boolean> {
|
|
166
166
|
const { systemPrompt, signal } = opts;
|
|
167
167
|
signal?.throwIfAborted();
|
|
168
168
|
const canonicalMessages = await this.opts.canonicalMessages();
|
|
169
169
|
const tools = this.compile(opts.tools);
|
|
170
|
+
let modelTurns = 0;
|
|
171
|
+
let reachedModelTurnLimit = false;
|
|
170
172
|
const pi = new PiCore({
|
|
171
173
|
convertToLlm,
|
|
172
174
|
streamFn: withProviderRetry(
|
|
@@ -175,6 +177,10 @@ class PiTurnAdapter {
|
|
|
175
177
|
getApiKey: () => this.opts.apiKey,
|
|
176
178
|
transformContext: this.opts.transformContext,
|
|
177
179
|
afterToolCall: this.governance.afterToolCall,
|
|
180
|
+
shouldStopAfterTurn: () => {
|
|
181
|
+
reachedModelTurnLimit = ++modelTurns >= MAX_MODEL_TURNS;
|
|
182
|
+
return signal?.aborted === true || reachedModelTurnLimit;
|
|
183
|
+
},
|
|
178
184
|
initialState: {
|
|
179
185
|
model: this.opts.pi.model,
|
|
180
186
|
systemPrompt,
|
|
@@ -203,6 +209,7 @@ class PiTurnAdapter {
|
|
|
203
209
|
pi.abort();
|
|
204
210
|
}
|
|
205
211
|
await running;
|
|
212
|
+
return reachedModelTurnLimit;
|
|
206
213
|
} finally {
|
|
207
214
|
signal?.removeEventListener("abort", abort);
|
|
208
215
|
unsubscribe();
|
|
@@ -524,7 +531,6 @@ export class PreparedPiTurnAdapter {
|
|
|
524
531
|
state.snapshot.pi.model.id,
|
|
525
532
|
),
|
|
526
533
|
canonicalMessages: options.canonicalMessages,
|
|
527
|
-
denyTools: descriptor.deny,
|
|
528
534
|
settle: options.durability.settleTool,
|
|
529
535
|
onToolTelemetry: options.onToolTelemetry,
|
|
530
536
|
transformContext: options.transformContext,
|
|
@@ -597,7 +603,7 @@ export class PreparedPiTurnAdapter {
|
|
|
597
603
|
options: PiPreparedTurnRunOptions,
|
|
598
604
|
): Promise<void> {
|
|
599
605
|
this.terminalIntent = undefined;
|
|
600
|
-
await this.turn.run(
|
|
606
|
+
const reachedModelTurnLimit = await this.turn.run(
|
|
601
607
|
{
|
|
602
608
|
systemPrompt: this.systemPrompt,
|
|
603
609
|
tools: this.candidates,
|
|
@@ -605,6 +611,12 @@ export class PreparedPiTurnAdapter {
|
|
|
605
611
|
},
|
|
606
612
|
(event) => this.handleEvent(event),
|
|
607
613
|
);
|
|
614
|
+
if (reachedModelTurnLimit && !this.terminalIntent) {
|
|
615
|
+
this.terminalIntent = {
|
|
616
|
+
outcome: "failed",
|
|
617
|
+
message: `Pi stopped after ${MAX_MODEL_TURNS} model turns`,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
608
620
|
if (this.terminalIntent) {
|
|
609
621
|
await this.options.onTerminal(this.terminalIntent);
|
|
610
622
|
}
|
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
import type { ApprovalReceipt } from "../../kernel/receipts";
|
|
7
7
|
import { boundDurableToolOutput } from "../../layers/context/budget/gate";
|
|
8
8
|
import {
|
|
9
|
-
EXECUTION_LEVELS,
|
|
10
9
|
type ExecutionLevel,
|
|
11
10
|
} from "../../lib/execution-level";
|
|
12
11
|
|
|
@@ -14,7 +13,7 @@ import {
|
|
|
14
13
|
|
|
15
14
|
// #region Public contracts
|
|
16
15
|
|
|
17
|
-
/**
|
|
16
|
+
/** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
|
|
18
17
|
export interface PiToolCandidate {
|
|
19
18
|
readonly owner: string;
|
|
20
19
|
readonly authorized: boolean;
|
|
@@ -26,7 +25,6 @@ export interface PiToolCandidate {
|
|
|
26
25
|
|
|
27
26
|
/** 控制候选 Pi 工具如何被编译为可执行工具集。 */
|
|
28
27
|
export interface CompilePiToolsOptions {
|
|
29
|
-
readonly deny?: readonly string[];
|
|
30
28
|
readonly governance?: PiToolGovernance;
|
|
31
29
|
readonly settle: (
|
|
32
30
|
call: SettledPiToolCall,
|
|
@@ -248,7 +246,7 @@ export function createPiToolGovernance(
|
|
|
248
246
|
}
|
|
249
247
|
|
|
250
248
|
// 为一个候选工具包上输出限额、持久化结算、重试治理和遥测。
|
|
251
|
-
// `compilePiTools()`
|
|
249
|
+
// `compilePiTools()` 对最终 Tool Surface 中的每个候选项调用。
|
|
252
250
|
// 一个共享包装边界可确保所有工具无论成功还是失败都经过 settle,不能由各工具自行选择是否持久化。
|
|
253
251
|
function governedTool(
|
|
254
252
|
candidate: PiToolCandidate,
|
|
@@ -402,11 +400,9 @@ function governedTool(
|
|
|
402
400
|
}
|
|
403
401
|
|
|
404
402
|
/**
|
|
405
|
-
*
|
|
403
|
+
* 把已经 Tool Surface 最终化的候选项包成可执行工具集。
|
|
406
404
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* 工具名是模型调用和结算的唯一键,所以冲突必须在组装时失败,不能用后来者静默覆盖。
|
|
405
|
+
* `PiTurnAdapter` 在每个 Turn 及手动重试时用它创建执行治理包装。
|
|
410
406
|
*/
|
|
411
407
|
export function compilePiTools(
|
|
412
408
|
candidates: readonly PiToolCandidate[],
|
|
@@ -415,30 +411,7 @@ export function compilePiTools(
|
|
|
415
411
|
if (!options?.settle) {
|
|
416
412
|
throw new Error("Pi Tool settlement port is required");
|
|
417
413
|
}
|
|
418
|
-
|
|
419
|
-
const tools: AgentTool<any, any>[] = [];
|
|
420
|
-
const owners = new Map<string, string>();
|
|
421
|
-
|
|
422
|
-
for (const candidate of candidates) {
|
|
423
|
-
if (!EXECUTION_LEVELS.includes(candidate.requiredExecutionLevel)) {
|
|
424
|
-
throw new Error(
|
|
425
|
-
`Pi Tool "${candidate.tool.name}" has invalid requiredExecutionLevel`,
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
if (!candidate.authorized || deny.has(candidate.tool.name)) continue;
|
|
429
|
-
|
|
430
|
-
const existingOwner = owners.get(candidate.tool.name);
|
|
431
|
-
if (existingOwner !== undefined) {
|
|
432
|
-
throw new Error(
|
|
433
|
-
`duplicate tool key "${candidate.tool.name}" (${candidate.owner} collides with ${existingOwner})`,
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
owners.set(candidate.tool.name, candidate.owner);
|
|
438
|
-
tools.push(governedTool(candidate, options));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return tools;
|
|
414
|
+
return candidates.map((candidate) => governedTool(candidate, options));
|
|
442
415
|
}
|
|
443
416
|
|
|
444
417
|
// #endregion
|
package/src/pi/tool/core-host.ts
CHANGED
|
@@ -8,27 +8,28 @@ import {
|
|
|
8
8
|
type WorkspaceFsLike,
|
|
9
9
|
} from "@cloudflare/shell";
|
|
10
10
|
import { StateConnector } from "@cloudflare/shell/workers";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import type {
|
|
12
|
+
RuntimeCodeExecutionPort,
|
|
13
|
+
WorkspacePort,
|
|
14
|
+
} from "../../kernel/bindings";
|
|
14
15
|
|
|
15
16
|
// 本文件沿用 `../../index.ts` 入口定义的 Workspace、Port 和 Tool Candidate 术语。
|
|
16
17
|
|
|
17
18
|
const CODEMODE_SANDBOX_TIMEOUT_MS = 55_000;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
* 把宿主的 Worker Loader、出站网络和 Workspace
|
|
21
|
+
* 把宿主的 Worker Loader、出站网络和 Workspace 组装成代码执行 Port。
|
|
21
22
|
*
|
|
22
23
|
* Worker 宿主在具备 Durable Object state 和完整平台绑定时调用,然后把返回值交给 Runtime 工具组装。
|
|
23
24
|
*
|
|
24
25
|
* Cloudflare Codemode 把持久化 Runtime 和无状态 Dynamic Worker executor 分开;这里必须用 StateConnector 显式注入已限定范围的 Workspace,并在返回模型前用官方 `truncateResult` 限制结果。
|
|
25
26
|
*/
|
|
26
|
-
export function
|
|
27
|
+
export function createWorkspaceCodeExecutionPort(options: {
|
|
27
28
|
readonly ctx: DurableObjectState;
|
|
28
29
|
readonly loader: WorkerLoader;
|
|
29
30
|
readonly outbound: Fetcher;
|
|
30
31
|
readonly workspace: WorkspacePort;
|
|
31
|
-
}):
|
|
32
|
+
}): RuntimeCodeExecutionPort {
|
|
32
33
|
const runtime = createCodemodeRuntime({
|
|
33
34
|
ctx: options.ctx,
|
|
34
35
|
executor: new DynamicWorkerExecutor({
|
|
@@ -48,5 +49,7 @@ export function workspaceCodeExecutionPiToolCandidate(options: {
|
|
|
48
49
|
name: "execute",
|
|
49
50
|
transformResult: truncateResult,
|
|
50
51
|
});
|
|
51
|
-
return
|
|
52
|
+
return {
|
|
53
|
+
execute: (input) => runtime.execute(input),
|
|
54
|
+
};
|
|
52
55
|
}
|