@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.10

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 (172) hide show
  1. package/package.json +7 -7
  2. package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
  3. package/src/adapter/cloudflare/agent.ts +1682 -0
  4. package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
  5. package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +37 -42
  6. package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
  7. package/src/adapter/cloudflare/index.ts +18 -0
  8. package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
  9. package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
  10. package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
  11. package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
  12. package/src/{db → adapter/cloudflare}/schema.ts +28 -80
  13. package/src/adapter/cloudflare/subagent/definition.ts +63 -33
  14. package/src/adapter/cloudflare/subagent/runner.ts +174 -120
  15. package/src/adapter/cloudflare/subagent/tools.ts +13 -14
  16. package/src/adapter/cloudflare/universal-agent/preparation.ts +7 -7
  17. package/src/adapter/cloudflare/universal-agent/tools.ts +10 -12
  18. package/src/adapter/cloudflare/workspace/git-fs.ts +1 -1
  19. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +1 -1
  20. package/src/adapter/cloudflare/workspace/version-control.ts +2 -2
  21. package/src/config/activation.ts +140 -0
  22. package/src/{runtime-agent-context.ts → config/agent-context.ts} +3 -3
  23. package/src/{runtime-assembler.ts → config/assembler.ts} +45 -46
  24. package/src/{kernel/runtime-assembly.ts → config/assembly-projection.ts} +4 -4
  25. package/src/{pi/assembly/snapshot.ts → config/assembly-snapshot.ts} +6 -6
  26. package/src/{runtime-definition.ts → config/definition.ts} +13 -13
  27. package/src/config/harness-runtime.ts +190 -0
  28. package/src/config/index.ts +14 -0
  29. package/src/config/model-guidance.ts +21 -0
  30. package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +60 -19
  31. package/src/{kernel/bindings.ts → config/ports.ts} +32 -16
  32. package/src/{kernel → config}/profile.ts +7 -0
  33. package/src/{kernel → config}/public-contracts.ts +6 -6
  34. package/src/{kernel → config}/runtime-load.ts +1 -1
  35. package/src/{pi/assembly/context.ts → context/assembly.ts} +5 -207
  36. package/src/{pi/assembly/extensions.ts → context/extension/assembly.ts} +5 -5
  37. package/src/context/extension/index.ts +3 -0
  38. package/src/context/index.ts +6 -0
  39. package/src/{layers/context → context/memory}/budget/gate.ts +21 -5
  40. package/src/conversation/harness-resume.ts +288 -0
  41. package/src/conversation/harness-stream.ts +99 -0
  42. package/src/{pi/runtime-adapter/transcript.ts → conversation/history.ts} +206 -233
  43. package/src/conversation/index.ts +6 -0
  44. package/src/{pi → conversation}/message/contract.ts +3 -0
  45. package/src/{pi → conversation}/message/conversion.ts +75 -0
  46. package/src/{pi → conversation}/message/index.ts +1 -0
  47. package/src/{pi → conversation}/message/model-input.ts +1 -1
  48. package/src/{pi → conversation}/message/projection.ts +113 -30
  49. package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
  50. package/src/conversation/state.ts +196 -0
  51. package/src/harness/index.ts +7 -0
  52. package/src/harness/session-harness.ts +562 -0
  53. package/src/harness/submission-operation.ts +49 -0
  54. package/src/index.ts +38 -43
  55. package/src/lib/artifacts.ts +18 -0
  56. package/src/lib/execution-level.ts +13 -0
  57. package/src/lib/mcp.ts +1 -1
  58. package/src/lib/prompt.ts +7 -6
  59. package/src/lib/token-estimate.ts +8 -0
  60. package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -17
  61. package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
  62. package/src/{pi/session → session/cloudflare}/migration.ts +404 -14
  63. package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
  64. package/src/session/index.ts +39 -0
  65. package/src/skills/springbrand-worker-website/index.ts +1 -1
  66. package/src/submission/admission.ts +286 -0
  67. package/src/{kernel/receipts.ts → submission/contracts.ts} +56 -2
  68. package/src/submission/delivery.ts +212 -0
  69. package/src/submission/finalization.ts +111 -0
  70. package/src/submission/index.ts +9 -0
  71. package/src/{kernel/submission-lifecycle.ts → submission/lifecycle.ts} +41 -28
  72. package/src/{db/submission.repo.ts → submission/repository.ts} +117 -114
  73. package/src/submission/steer-delivery.ts +167 -0
  74. package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
  75. package/src/submission/unattended.ts +40 -0
  76. package/src/telemetry/contract.ts +12 -0
  77. package/src/telemetry/coordinator.ts +65 -0
  78. package/src/telemetry/delivery.ts +2 -2
  79. package/src/telemetry/index.ts +5 -0
  80. package/src/{model-usage.ts → telemetry/model-usage.ts} +1 -1
  81. package/src/telemetry/recorder.ts +1 -1
  82. package/src/telemetry/runtime-telemetry.ts +12 -0
  83. package/src/{kernel/durable-lifecycle.ts → telemetry/subagent-lifecycle.ts} +1 -1
  84. package/src/telemetry/turn-events.ts +279 -0
  85. package/src/tools/README.md +44 -0
  86. package/src/{pi/tool/ai-adapter.ts → tools/adapters/ai-to-pi.ts} +6 -9
  87. package/src/{pi/tool → tools/adapters}/declared.ts +6 -2
  88. package/src/{pi/tool/nested-tools.ts → tools/adapters/pi-to-ai.ts} +2 -2
  89. package/src/tools/assistant/ask-user.ts +317 -0
  90. package/src/tools/assistant/candidate.ts +15 -0
  91. package/src/tools/assistant/followups.ts +34 -0
  92. package/src/tools/assistant/index.ts +19 -0
  93. package/src/tools/assistant/memory.ts +73 -0
  94. package/src/tools/assistant/plan.ts +98 -0
  95. package/src/{pi/tool → tools/assistant}/time.ts +1 -1
  96. package/src/{pi/tool/core-host.ts → tools/codemode/adapter.ts} +50 -49
  97. package/src/{pi/tool/core.ts → tools/codemode/index.ts} +35 -91
  98. package/src/tools/codemode/workspace-connector.ts +198 -0
  99. package/src/tools/extension/index.ts +42 -0
  100. package/src/tools/index.ts +32 -0
  101. package/src/{pi/tool → tools/mcp}/gateway.ts +19 -19
  102. package/src/{pi/tool/mcp.ts → tools/mcp/index.ts} +5 -5
  103. package/src/tools/runtime/approval/index.ts +1 -0
  104. package/src/{db/approval.repo.ts → tools/runtime/approval/repository.ts} +22 -41
  105. package/src/tools/runtime/compiler.ts +264 -0
  106. package/src/tools/runtime/contracts.ts +117 -0
  107. package/src/tools/runtime/decision-gate.ts +337 -0
  108. package/src/tools/runtime/human-decisions.ts +127 -0
  109. package/src/tools/runtime/interaction/index.ts +1 -0
  110. package/src/{db/interaction.repo.ts → tools/runtime/interaction/repository.ts} +1 -7
  111. package/src/tools/runtime/result.ts +22 -0
  112. package/src/tools/runtime/unattended-error.ts +4 -0
  113. package/src/tools/sandbox/index.ts +228 -0
  114. package/src/tools/schedule/index.ts +301 -0
  115. package/src/tools/skill/contracts.ts +20 -0
  116. package/src/tools/skill/index.ts +99 -0
  117. package/src/tools/skill/resources.ts +322 -0
  118. package/src/tools/skill/scripts.ts +168 -0
  119. package/src/{kernel/submission-authority.ts → tools/subagent/authority.ts} +31 -31
  120. package/src/{pi/tool/subagent.ts → tools/subagent/index.ts} +33 -36
  121. package/src/{db/agent-tool.repo.ts → tools/subagent/repository.ts} +1 -1
  122. package/src/{kernel/subagent-runtime.ts → tools/subagent/runtime.ts} +1 -1
  123. package/src/tools/subagent/temporary/approvals.ts +123 -0
  124. package/src/{layers/orchestration/temporary-agent/core.ts → tools/subagent/temporary/coordinator.ts} +41 -49
  125. package/src/tools/subagent/temporary/core.ts +53 -0
  126. package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/runner.ts +1 -1
  127. package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/workspace.ts +1 -4
  128. package/src/{pi/tool/web-fetch.ts → tools/web/fetch.ts} +4 -13
  129. package/src/tools/web/index.ts +2 -0
  130. package/src/tools/web/search/api.ts +1320 -0
  131. package/src/tools/web/search/index.ts +2 -0
  132. package/src/{pi/tool/web-search/web-search.ts → tools/web/search/search.ts} +5 -7
  133. package/src/{pi/tool/workspace-sandbox.ts → tools/workspace/index.ts} +12 -272
  134. package/src/{pi/tool/workspace-revision.ts → tools/workspace/revision.ts} +2 -2
  135. package/src/db/milestone.repo.ts +0 -104
  136. package/src/db/settlement.repo.ts +0 -104
  137. package/src/kernel/approval-lifecycle.ts +0 -638
  138. package/src/kernel/interaction-lifecycle.ts +0 -433
  139. package/src/kernel/recoverable-chat-agent.ts +0 -1020
  140. package/src/kernel/state.ts +0 -91
  141. package/src/pi/assembly/index.ts +0 -19
  142. package/src/pi/runtime-adapter/execution.ts +0 -1163
  143. package/src/pi/runtime-adapter/index.ts +0 -293
  144. package/src/pi/runtime-adapter/recovery.ts +0 -982
  145. package/src/pi/session/index.ts +0 -24
  146. package/src/pi/tool/base.ts +0 -550
  147. package/src/pi/tool/compiler.ts +0 -483
  148. package/src/pi/tool/index.ts +0 -38
  149. package/src/pi/tool/schedule.ts +0 -261
  150. package/src/pi/tool/skill.ts +0 -565
  151. package/src/pi/tool/web-search/api.ts +0 -1318
  152. package/src/pi/tool/web-search/index.ts +0 -2
  153. package/src/pi/tool/workspace-state-connector.ts +0 -146
  154. package/src/pi/turn/approval.ts +0 -181
  155. package/src/pi/turn/index.ts +0 -82
  156. package/src/pi/turn/interaction.ts +0 -181
  157. package/src/runtime.ts +0 -4760
  158. package/src/{kernel/runtime-assembly-view.ts → config/assembly-view.ts} +0 -0
  159. package/src/{kernel → config}/degradation.ts +0 -0
  160. package/src/{kernel → config}/runtime-config.ts +0 -0
  161. package/src/{kernel/extensions.ts → context/extension/contracts.ts} +0 -0
  162. package/src/{db/ext-context.repo.ts → context/extension/repository.ts} +0 -0
  163. package/src/{pi/runtime-adapter/openrouter-messages.ts → models/openrouter/messages.ts} +0 -0
  164. package/src/{pi/runtime-adapter → models/openrouter}/test-fixtures/openrouter-tool-search.sse +0 -0
  165. package/src/{db/submission-admission.repo.ts → submission/admission-repository.ts} +0 -0
  166. package/src/{db/telemetry-outbox.repo.ts → telemetry/repository.ts} +0 -0
  167. package/src/{db/runtime-event-outbox.repo.ts → telemetry/runtime-event-repository.ts} +0 -0
  168. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/contract.ts +0 -0
  169. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/extract/index.ts +0 -0
  170. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/fanout/index.ts +0 -0
  171. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/registry.ts +1 -1
  172. /package/src/{workspace-versioning.ts → tools/workspace/versioning.ts} +0 -0
@@ -1,483 +0,0 @@
1
- import type {
2
- AgentLoopConfig,
3
- AgentTool,
4
- AgentToolResult,
5
- } from "@earendil-works/pi-agent-core";
6
- import type { ApprovalReceipt } from "../../kernel/receipts";
7
- import {
8
- boundDurableToolOutput,
9
- type BudgetPolicy,
10
- spillDurableToolOutput,
11
- } from "../../layers/context/budget/gate";
12
- import {
13
- type ArtifactRef,
14
- serializeOutput,
15
- type SpillWorkspace,
16
- } from "../../lib/artifacts";
17
- import {
18
- type ExecutionLevel,
19
- } from "../../lib/execution-level";
20
-
21
- // 本文件沿用 `../../index.ts` 入口定义的 Tool Candidate、Tool Settlement 和 Governance 术语。
22
-
23
- // #region Public contracts
24
-
25
- /**
26
- * 声明一个 Tool 的结果由客户端投递,而不是由 `execute()` 产出。
27
- *
28
- * 带这个字段的 Tool 会在执行链里 park 住,等宿主经 `respondToolInteraction` 把响应送进来,
29
- * 再用 `settle` 把响应映射成这次 `toolCallId` 的权威 ToolResult。它的 `execute` 不会被调用。
30
- *
31
- * 这是 AI SDK 里「没有 execute 的 user-interaction tool」的等价物;差别是我们 park 住整个 Turn,
32
- * 而不是让 tool part 悬空到下一次请求 —— 我们的 transcript 是服务端权威的,悬空的 tool_use
33
- * 会让下一次模型调用非法。
34
- */
35
- export interface PiToolInteractionSpec {
36
- /** 结合 Tool 输入校验客户端响应体;不通过则拒绝投递,park 保持不变。 */
37
- readonly validateResponse: (input: unknown, response: unknown) => boolean;
38
- /** 把通过校验的响应映射成 ToolResult;缺省是原样回传。 */
39
- readonly settle?: (
40
- input: unknown,
41
- response: unknown,
42
- ) => Omit<AgentToolResult<unknown>, "terminate">;
43
- }
44
-
45
- /** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
46
- export type ToolExposureMode = "direct" | "codemode" | "both";
47
-
48
- export interface PiToolCandidate {
49
- readonly owner: string;
50
- readonly tool: AgentTool<any, any>;
51
- /** @internal Send the complete schema but hide it until provider Tool Search finds it. */
52
- readonly deferLoading?: true;
53
- /** Omitted means visible both directly and through Code Mode. */
54
- readonly exposureMode?: ToolExposureMode;
55
- /** Conservative maximum used in the stable Runtime descriptor. */
56
- readonly requiredExecutionLevel: ExecutionLevel;
57
- /** Trusted parameter-level policy, evaluated before approval or dispatch. */
58
- readonly requiredExecutionLevelForInput?: (
59
- input: unknown,
60
- ) => ExecutionLevel | Promise<ExecutionLevel>;
61
- /**
62
- * 无条件需要人来决定:不看执行档位矩阵,每次调用都 park 出审批。
63
- *
64
- * 档位差是「够不够格自己跑」,不是「该不该问人」。花用户钱、改变账户
65
- * 状态这类工具,一旦 Agent 档位被 `allow_level` 永久拉高就会静默执行,
66
- * 那是失效不是降级。`deny` 仍然优先,被 deny 的工具根本不会注入。
67
- */
68
- readonly alwaysRequiresApproval?: boolean;
69
- /** Recovery may replay this Tool Call only when the candidate guarantees convergence. Omitted means non-idempotent. */
70
- readonly retry?: "idempotent" | "non-idempotent";
71
- readonly summary?: string;
72
- readonly source?: ApprovalReceipt["source"];
73
- readonly interaction?: PiToolInteractionSpec;
74
- /** Large payloads spill by default; protocol-shaped results opt into structure. */
75
- readonly outputBudget?: BudgetPolicy;
76
- }
77
-
78
- /** 控制候选 Pi 工具如何被编译为可执行工具集。 */
79
- export interface CompilePiToolsOptions {
80
- readonly governance?: PiToolGovernance;
81
- readonly workspace?: SpillWorkspace;
82
- readonly settle: (
83
- call: SettledPiToolCall,
84
- ) => void | Promise<void>;
85
- }
86
-
87
- const governanceState = Symbol("PiToolGovernanceState");
88
-
89
- /** 向 Pi Agent 提供工具后置钩子,并为编译器保留同一 Turn 的治理状态。 */
90
- export interface PiToolGovernance {
91
- readonly afterToolCall: NonNullable<AgentLoopConfig["afterToolCall"]>;
92
- readonly wrapUpRequested: () => boolean;
93
- readonly [governanceState]: PiToolGovernanceState;
94
- }
95
-
96
- /** 描述一次已进入持久化结算边界的 Pi 工具调用。 */
97
- export interface SettledPiToolCall {
98
- readonly toolCallId: string;
99
- readonly toolName: string;
100
- readonly args: unknown;
101
- readonly result: AgentToolResult<unknown>;
102
- readonly isError: boolean;
103
- /** Diagnostic-only original error; callers must not persist or expose it. */
104
- readonly cause?: unknown;
105
- readonly summary?: string;
106
- readonly source?: ApprovalReceipt["source"];
107
- }
108
-
109
- const MAX_TOOL_RETRIES = 3;
110
- const TURN_FAILURE_CEILING = 8;
111
- // DO SQLite rows also contain arguments and transcript/recovery envelopes.
112
- // Keep each serialized result below half the 2 MiB row limit.
113
- const DURABLE_TOOL_RESULT_MAX_BYTES = 1024 * 1024;
114
- const MEDIA_CAPTION_MAX_CHARS = 4 * 1024;
115
-
116
- interface PiToolGovernanceState {
117
- readonly failures: Map<string, number>;
118
- readonly terminalCalls: Set<string>;
119
- totalFailures: number;
120
- aborted: boolean;
121
- }
122
-
123
- // #endregion
124
-
125
- // #region Governance helpers
126
-
127
- // 把任意失败值收敛为可安全显示的简短消息。
128
- // 受治理工具在工具执行或结算抛错时调用它。
129
- // 读取错误自身也可能抛错,因此最外层必须保留固定回退文本。
130
- function errorMessage(error: unknown): string {
131
- try {
132
- if (error instanceof Error) {
133
- return error.message || error.name;
134
- }
135
- return String(error);
136
- } catch {
137
- return "Tool execution failed";
138
- }
139
- }
140
-
141
- // 把工具参数转成可用于重试键的稳定文本。
142
- // `callKey()` 为每次工具调用计算相同输入的失败次数时调用。
143
- // JSON 序列化失败时保留 String 回退,以避免循环引用直接使治理失效。
144
- // TODO(待确认): `String(value)` 本身仍可能抛错,需确认是否要把该极端输入收敛为固定文本。
145
- function safeStringify(value: unknown): string {
146
- try {
147
- return JSON.stringify(value) ?? "∅";
148
- } catch {
149
- return String(value);
150
- }
151
- }
152
-
153
- // 把工具名和参数压缩成同一 Turn 内的重试计数键。
154
- // `recordFailure()`、`blockReason()` 和成功后的计数清理都调用它。
155
- // 不直接把完整参数存入 Map,可避免 Turn 内重复保留大输入。
156
- // TODO(待确认): 32 位哈希冲突会把不同参数当成同一次重试,需确认当前误拦截风险是否可接受。
157
- function callKey(toolName: string, args: unknown): string {
158
- const input = safeStringify(args);
159
- let hash = 0x811c9dc5;
160
- for (let index = 0; index < input.length; index += 1) {
161
- hash ^= input.charCodeAt(index);
162
- hash = Math.imul(hash, 0x01000193);
163
- }
164
- return `${toolName}\0${(hash >>> 0).toString(36)}`;
165
- }
166
-
167
- // Most Pi adapters keep the original Tool value in details and its serialized
168
- // model form in one text block. Spill that original once instead of duplicating
169
- // it inside an AgentToolResult JSON envelope.
170
- function spillPayload(result: AgentToolResult<unknown>): unknown {
171
- const text = result.content.length === 1 && result.content[0]?.type === "text"
172
- ? result.content[0].text
173
- : undefined;
174
- if (text !== undefined && serializeOutput(result.details).text === text) {
175
- return result.details;
176
- }
177
- return result;
178
- }
179
-
180
- function artifactResult(
181
- result: AgentToolResult<unknown>,
182
- ref: ArtifactRef,
183
- ): AgentToolResult<unknown> {
184
- let captionRemaining = MEDIA_CAPTION_MAX_CHARS;
185
- const mediaContent = result.content.some((part) => part.type !== "text")
186
- ? result.content.flatMap((part): AgentToolResult<unknown>["content"] => {
187
- if (part.type !== "text") return [part];
188
- const text = part.text.slice(0, captionRemaining);
189
- captionRemaining -= text.length;
190
- return text ? [{ ...part, text }] : [];
191
- })
192
- : [];
193
- return {
194
- ...result,
195
- content: [{
196
- type: "text",
197
- text: [
198
- ref.note,
199
- `Path: ${ref.path}`,
200
- `Bytes: ${ref.bytes}`,
201
- `Preview:\n${ref.preview}`,
202
- ].join("\n"),
203
- }, ...mediaContent],
204
- details: ref,
205
- };
206
- }
207
-
208
- async function durableResult(
209
- result: AgentToolResult<unknown>,
210
- candidate: PiToolCandidate,
211
- workspace: SpillWorkspace | undefined,
212
- ): Promise<AgentToolResult<unknown>> {
213
- const spill = (candidate.outputBudget ?? { kind: "spill" }).kind === "spill";
214
- let ref = spill
215
- ? await spillDurableToolOutput(spillPayload(result), { workspace })
216
- : null;
217
- const bounded = ref
218
- ? artifactResult(result, ref)
219
- : boundDurableToolOutput(result) as AgentToolResult<unknown>;
220
- const bytes = new TextEncoder().encode(JSON.stringify(bounded)).byteLength;
221
- if (bytes <= DURABLE_TOOL_RESULT_MAX_BYTES) return bounded;
222
-
223
- // Binary blocks cannot be truncated, and this boundary has no image resizer.
224
- // Preserve the original where possible and fail before any SQLite write.
225
- if (!spill) ref = await spillDurableToolOutput(spillPayload(result), { workspace });
226
- throw new Error(
227
- `Tool output exceeds the 1 MiB durable result limit (${bytes} bytes). ` +
228
- "Return a smaller result, such as lower-resolution images or fewer media items; the original cannot be included inline. " +
229
- (ref ? `Original output saved to Workspace: ${ref.path}` : "The original output could not be saved to Workspace."),
230
- );
231
- }
232
-
233
- async function untilAborted<T>(
234
- signal: AbortSignal | undefined,
235
- run: () => T | Promise<T>,
236
- ): Promise<T> {
237
- // ponytail: 这会立即释放 Turn,但无法杀死不配合的进程内副作用;需要物理终止的 Tool 必须放进可销毁的隔离执行器。
238
- signal?.throwIfAborted();
239
- if (!signal) return run();
240
- let onAbort!: () => void;
241
- const aborted = new Promise<never>((_resolve, reject) => {
242
- onAbort = () => reject(
243
- signal.reason ?? new DOMException("Aborted", "AbortError"),
244
- );
245
- signal.addEventListener("abort", onAbort, { once: true });
246
- });
247
- try {
248
- return await Promise.race([run(), aborted]);
249
- } finally {
250
- signal.removeEventListener("abort", onAbort);
251
- }
252
- }
253
-
254
- // 记录一次工具失败并在 Turn 达到上限时打开断路器。
255
- // 受治理 execute 方法在工具或结算失败后调用。
256
- // 同时记录单输入次数和 Turn 总数,是为了既阻止原样重试又防止换参数无限失败。
257
- function recordFailure(
258
- state: PiToolGovernanceState | undefined,
259
- toolCallId: string,
260
- toolName: string,
261
- args: unknown,
262
- ): void {
263
- if (!state) return;
264
- const key = callKey(toolName, args);
265
- state.failures.set(key, (state.failures.get(key) ?? 0) + 1);
266
- state.totalFailures += 1;
267
- if (state.totalFailures >= TURN_FAILURE_CEILING) {
268
- state.aborted = true;
269
- state.terminalCalls.add(toolCallId);
270
- }
271
- }
272
-
273
- // 在执行前判断这次工具调用是否已超过重试或 Turn 上限。
274
- // 受治理 execute 方法对每个调用首先调用它。
275
- // 被阻断的 call id 必须记入 terminalCalls,才能让 Pi 在返回错误后进入无工具收尾回合。
276
- function blockReason(
277
- state: PiToolGovernanceState | undefined,
278
- toolCallId: string,
279
- toolName: string,
280
- args: unknown,
281
- ): string | undefined {
282
- if (!state) return;
283
- if (state.aborted) {
284
- state.terminalCalls.add(toolCallId);
285
- return "Turn circuit breaker tripped: too many tool failures this turn. Stop calling tools now.";
286
- }
287
- if (
288
- (state.failures.get(callKey(toolName, args)) ?? 0) >=
289
- MAX_TOOL_RETRIES
290
- ) {
291
- state.terminalCalls.add(toolCallId);
292
- return `This exact ${toolName} call has already failed ${MAX_TOOL_RETRIES} times — change the input or approach instead of retrying it verbatim.`;
293
- }
294
- }
295
-
296
- // #endregion
297
-
298
- // #region Governance and compilation
299
-
300
- /**
301
- * 为一个 Pi Turn 创建共享的工具重试治理状态。
302
- *
303
- * `PiTurnAdapter` 在准备 Turn 时调用,并同时把返回的 `afterToolCall` 交给 Pi Agent。
304
- *
305
- * 状态用 symbol 绑定到钩子对象,让编译后的工具和 Pi 后置钩子共享一份 Turn 计数而不暴露公开可变 API。
306
- */
307
- export function createPiToolGovernance(): PiToolGovernance {
308
- const state: PiToolGovernanceState = {
309
- failures: new Map(),
310
- terminalCalls: new Set(),
311
- totalFailures: 0,
312
- aborted: false,
313
- };
314
- const governance: PiToolGovernance = {
315
- [governanceState]: state,
316
- // Pi Agent 在每次工具执行结束后调用,并传入实际 toolCall id。只消费
317
- // terminalCalls 中的 id,可避免一次失败误触发收尾;不能在这里直接
318
- // terminate,否则模型没有机会产出权威 assistant 消息。
319
- async afterToolCall(context) {
320
- if (!state.terminalCalls.delete(context.toolCall.id)) {
321
- return;
322
- }
323
- state.aborted = true;
324
- },
325
- wrapUpRequested: () => state.aborted,
326
- };
327
- return governance;
328
- }
329
-
330
- // 为一个候选工具包上输出限额、持久化结算和重试治理。
331
- // `compilePiTools()` 对最终 Tool Surface 中的每个候选项调用。
332
- // 一个共享包装边界可确保所有工具无论成功还是失败都经过 settle,不能由各工具自行选择是否持久化。
333
- function governedTool(
334
- candidate: PiToolCandidate,
335
- options: CompilePiToolsOptions,
336
- ): AgentTool<any, any> {
337
- const tool = candidate.tool.constrainedSampling === false
338
- ? candidate.tool
339
- : {
340
- ...candidate.tool,
341
- constrainedSampling: candidate.tool.constrainedSampling ?? {
342
- type: "json_schema" as const,
343
- strict: "prefer" as const,
344
- },
345
- };
346
- const execute = tool.execute;
347
- const state = options.governance?.[governanceState];
348
- const settle = options.settle;
349
-
350
- return {
351
- ...tool,
352
- ...(candidate.deferLoading ? { deferLoading: true as const } : {}),
353
- // 执行一次完整的受治理 Pi 工具调用。
354
- // Pi 工具循环选中编译后的工具时调用,调用方应传入稳定 toolCall id 供结算去重。
355
- // 输出限额必须发生在 settle 之前,否则 Durable Object 会持久化一份与模型最终所见不同的过大结果。
356
- async execute(toolCallId, args, signal, onUpdate) {
357
- const blocked = blockReason(
358
- state,
359
- toolCallId,
360
- candidate.tool.name,
361
- args,
362
- );
363
- if (blocked) {
364
- const cause = new Error(blocked);
365
- const result = {
366
- content: [{ type: "text" as const, text: blocked }],
367
- details: {},
368
- };
369
- try {
370
- await settle({
371
- toolCallId,
372
- toolName: candidate.tool.name,
373
- args,
374
- result,
375
- isError: true,
376
- cause,
377
- summary: candidate.summary,
378
- source: candidate.source,
379
- });
380
- } catch (settlementFailure) {
381
- throw settlementFailure;
382
- }
383
- throw cause;
384
- }
385
-
386
- let result: AgentToolResult<unknown>;
387
- const guardedUpdate: typeof onUpdate = onUpdate
388
- ? (update) => {
389
- if (!signal?.aborted) onUpdate(update);
390
- }
391
- : undefined;
392
- try {
393
- result = await untilAborted(
394
- signal,
395
- async () => durableResult(
396
- await execute(toolCallId, args, signal, guardedUpdate),
397
- candidate,
398
- options.workspace,
399
- ),
400
- );
401
- } catch (cause) {
402
- const message = errorMessage(cause);
403
- const result = boundDurableToolOutput({
404
- content: [{ type: "text", text: message }],
405
- details: {},
406
- }) as AgentToolResult<unknown>;
407
- let failure = cause;
408
- try {
409
- await settle({
410
- toolCallId,
411
- toolName: candidate.tool.name,
412
- args,
413
- result,
414
- isError: true,
415
- cause,
416
- summary: candidate.summary,
417
- source: candidate.source,
418
- });
419
- } catch (settlementFailure) {
420
- failure = settlementFailure;
421
- state?.terminalCalls.add(toolCallId);
422
- }
423
- recordFailure(
424
- state,
425
- toolCallId,
426
- candidate.tool.name,
427
- args,
428
- );
429
- const boundedMessage = (result.content[0] as { text: string }).text;
430
- throw cause instanceof Error &&
431
- failure === cause &&
432
- boundedMessage === cause.message
433
- ? failure
434
- : new Error(
435
- failure === cause ? boundedMessage : errorMessage(failure),
436
- { cause: failure },
437
- );
438
- }
439
-
440
- try {
441
- await settle({
442
- toolCallId,
443
- toolName: candidate.tool.name,
444
- args,
445
- result,
446
- isError: false,
447
- summary: candidate.summary,
448
- source: candidate.source,
449
- });
450
- } catch (cause) {
451
- state?.terminalCalls.add(toolCallId);
452
- recordFailure(
453
- state,
454
- toolCallId,
455
- candidate.tool.name,
456
- args,
457
- );
458
- throw cause;
459
- }
460
- state?.failures.delete(
461
- callKey(candidate.tool.name, args),
462
- );
463
- return result;
464
- },
465
- };
466
- }
467
-
468
- /**
469
- * 把已经 Tool Surface 最终化的候选项包成可执行工具集。
470
- *
471
- * `PiTurnAdapter` 在每个 Turn 及手动重试时用它创建执行治理包装。
472
- */
473
- export function compilePiTools(
474
- candidates: readonly PiToolCandidate[],
475
- options: CompilePiToolsOptions,
476
- ): AgentTool<any, any>[] {
477
- if (!options?.settle) {
478
- throw new Error("SpringBrand Tool settlement port is required");
479
- }
480
- return candidates.map((candidate) => governedTool(candidate, options));
481
- }
482
-
483
- // #endregion
@@ -1,38 +0,0 @@
1
- import {
2
- compilePiTools,
3
- createPiToolGovernance,
4
- } from "./compiler";
5
- import { listExtensionsPiToolCandidate } from "./core";
6
- import { createPiDeclaredToolCandidate } from "./declared";
7
- import { createPiMcpToolCandidates } from "./mcp";
8
-
9
- /**
10
- * 集中暴露 Pi 工具候选项的组装与治理入口。
11
- *
12
- * Runtime 组装层在收集内建、Extension 和 MCP 工具时使用,具体调用约定由各导出函数的 TSDoc 说明。
13
- *
14
- * 这里只是稳定的薄汇总,不复制工具注册状态;Tool Candidate、Tool Settlement 和 Governance 等核心术语统一见 `../../index.ts` 入口说明。
15
- */
16
- export const piTools = Object.freeze({
17
- compile: compilePiTools,
18
- createGovernance: createPiToolGovernance,
19
- createDeclaredCandidate: createPiDeclaredToolCandidate,
20
- createMcpCandidates: createPiMcpToolCandidates,
21
- listExtensionsCandidate: listExtensionsPiToolCandidate,
22
- });
23
-
24
- export * from "./ai-adapter";
25
- export * from "./base";
26
- export * from "./compiler";
27
- export * from "./core";
28
- export * from "./core-host";
29
- export * from "./declared";
30
- export * from "./gateway";
31
- export * from "./mcp";
32
- export * from "./schedule";
33
- export * from "./skill";
34
- export * from "./subagent";
35
- export * from "./time";
36
- export * from "./workspace-sandbox";
37
- export * from "./workspace-revision";
38
- export * from "./web-search";