@springbrand/agent-runtime 0.2.0-alpha.14 → 0.2.0-alpha.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/agent-runtime",
3
- "version": "0.2.0-alpha.14",
3
+ "version": "0.2.0-alpha.16",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -2,6 +2,7 @@ import type { SkillSource } from "agents/skills";
2
2
  import type { ScheduleSpec } from "./receipts";
3
3
  import type { RuntimeActivityProjection } from "./state";
4
4
  import type { ExecutionLevel } from "../lib/execution-level";
5
+ import type { RuntimeCodeExecutionFactory } from "../tool-registry";
5
6
  import type {
6
7
  RuntimeEventConfirmation,
7
8
  RuntimeLifecycleFact,
@@ -1024,6 +1025,7 @@ export interface RuntimeSkillSourceBinding {
1024
1025
  export interface RuntimeBindings {
1025
1026
  provider: RuntimeProviderPort;
1026
1027
  platform: RuntimePlatformPort;
1028
+ codeExecution?: RuntimeCodeExecutionFactory;
1027
1029
  /** Secret-capability binding; implementations must keep Runtime Grant in closure state. */
1028
1030
  gateway?: RuntimeGatewayPort;
1029
1031
  workspace?: WorkspacePort;
@@ -208,6 +208,7 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
208
208
  "bind_resource",
209
209
  "delete",
210
210
  "edit",
211
+ "execute",
211
212
  "find",
212
213
  "get_time",
213
214
  "grep",
@@ -25,6 +25,7 @@ import { PiChunkEncoder } from "../message";
25
25
  import type { UIMessageChunk } from "ai";
26
26
  import { ChatStreamStalledError } from "agents/chat";
27
27
  import {
28
+ codeExecutionPiToolCandidate,
28
29
  compilePiTools,
29
30
  createPiToolGovernance,
30
31
  normalizeUpdatePlanArguments,
@@ -48,6 +49,7 @@ import {
48
49
  import { EXECUTION_LEVELS } from "../../lib/execution-level";
49
50
  import { projectToolOutputForModel } from "../../layers/context/budget/gate";
50
51
  import type { SpillWorkspace } from "../../lib/artifacts";
52
+ import { toolRegistryFromPiCandidates } from "../../tool-registry";
51
53
 
52
54
  // #region Single-run Pi bridge
53
55
 
@@ -527,7 +529,10 @@ export class PreparedPiTurnAdapter {
527
529
  messageId: options.submission.messageId,
528
530
  startedAt: options.submission.startedAt,
529
531
  });
530
- this.candidates = state.candidates.map((candidate) => ({
532
+ const bindCandidate = (
533
+ candidate: PiToolCandidate,
534
+ toolCallIdPrefix?: string,
535
+ ): PiToolCandidate => ({
531
536
  ...candidate,
532
537
  tool: {
533
538
  ...candidate.tool,
@@ -535,6 +540,9 @@ export class PreparedPiTurnAdapter {
535
540
  // 实时运行由 PiCore 调用它,恢复或审批续跑则由 retryTool 进入同一路径。
536
541
  // 顺序不能随便调整:门禁先于结果复用,审批可能生成结果,不确定的非幂等工作不能重放。
537
542
  execute: async (toolCallId, input, signal, onUpdate) => {
543
+ if (toolCallIdPrefix) {
544
+ toolCallId = `${toolCallIdPrefix}:${toolCallId}`;
545
+ }
538
546
  const requiredExecutionLevel = candidate.requiredExecutionLevelForInput
539
547
  ? await candidate.requiredExecutionLevelForInput(input)
540
548
  : candidate.requiredExecutionLevel;
@@ -667,7 +675,35 @@ export class PreparedPiTurnAdapter {
667
675
  );
668
676
  },
669
677
  },
670
- }));
678
+ });
679
+ this.candidates = state.candidates.map((candidate) => {
680
+ if (!candidate.codeExecutionTools) return bindCandidate(candidate);
681
+ const factory = state.snapshot.bindings.codeExecution;
682
+ if (!factory) {
683
+ throw new Error("Prepared Code Mode Tool requires a Runtime factory");
684
+ }
685
+ return bindCandidate({
686
+ ...candidate,
687
+ tool: {
688
+ ...candidate.tool,
689
+ execute: (toolCallId, input, signal, onUpdate) => {
690
+ const runtimeCandidate = codeExecutionPiToolCandidate(
691
+ factory.create(toolRegistryFromPiCandidates(
692
+ candidate.codeExecutionTools!.map((inner) =>
693
+ bindCandidate(inner, toolCallId)
694
+ ),
695
+ )),
696
+ );
697
+ return runtimeCandidate.tool.execute(
698
+ toolCallId,
699
+ input,
700
+ signal,
701
+ onUpdate,
702
+ );
703
+ },
704
+ },
705
+ });
706
+ });
671
707
  this.turn = new PiTurnAdapter({
672
708
  pi: {
673
709
  model: state.snapshot.pi.model,
@@ -46,6 +46,8 @@ export interface PiToolInteractionSpec {
46
46
  export interface PiToolCandidate {
47
47
  readonly owner: string;
48
48
  readonly tool: AgentTool<any, any>;
49
+ /** @internal Tools hidden behind this Code Mode candidate. */
50
+ readonly codeExecutionTools?: readonly PiToolCandidate[];
49
51
  /** Conservative maximum used in the stable Runtime descriptor. */
50
52
  readonly requiredExecutionLevel: ExecutionLevel;
51
53
  /** Trusted parameter-level policy, evaluated before approval or dispatch. */
@@ -108,12 +108,12 @@ const executeParameters = Type.Object({
108
108
  const CODEMODE_EXECUTE_TIMEOUT_MS = 60_000;
109
109
 
110
110
  /**
111
- * 把 Cloudflare Codemode Runtime handle 包装为高风险 Pi 代码执行工具候选项。
111
+ * 把 Cloudflare Codemode Runtime handle 包装为 Pi 代码执行工具候选项。
112
112
  *
113
113
  * Tool Surface 收到 Host 已组装的 Code Execution Port 后调用,
114
114
  * 模型再通过 `execute` 运行代码。
115
115
  *
116
- * Codemode 代码可访问出站网络和已配置 connector,因此必须保留 high 档和 codemode source 标记,不能降级成普通本地计算工具。
116
+ * Code Mode 作为一个完整的 safe 工具对外暴露,内部能力不再单独提权。
117
117
  */
118
118
  export function codeExecutionPiToolCandidate(
119
119
  runtime: RuntimeCodeExecutionPort,
@@ -159,7 +159,7 @@ export function codeExecutionPiToolCandidate(
159
159
  };
160
160
  return {
161
161
  owner: "core:codemode",
162
- requiredExecutionLevel: "high",
162
+ requiredExecutionLevel: "safe",
163
163
  outputBudget: { kind: "structure" },
164
164
  source: "codemode",
165
165
  summary: "Run JavaScript with network and configured connector access",
@@ -252,12 +252,16 @@ async function createToolSurface(
252
252
  candidate.interaction ||
253
253
  typeof candidate.tool.execute !== "function",
254
254
  );
255
+ const codeExecutionTools = Object.freeze([...mergeable]);
255
256
  return Object.freeze([
256
- Object.freeze(codeExecutionPiToolCandidate(
257
- input.codeExecution.create(
258
- toolRegistryFromPiCandidates(mergeable),
257
+ Object.freeze({
258
+ ...codeExecutionPiToolCandidate(
259
+ input.codeExecution.create(
260
+ toolRegistryFromPiCandidates(codeExecutionTools),
261
+ ),
259
262
  ),
260
- )),
263
+ codeExecutionTools,
264
+ }),
261
265
  ...direct,
262
266
  ]);
263
267
  },
@@ -528,6 +532,7 @@ class RuntimeBuilder {
528
532
  const bindings: RuntimeBindings = Object.freeze({
529
533
  provider,
530
534
  platform: Object.freeze({ ...this.platform }),
535
+ ...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
531
536
  ...(this.gateway ? { gateway: this.gateway } : {}),
532
537
  ...(this.workspace ? { workspace: this.workspace } : {}),
533
538
  ...(this.memoryProfile.enabled && this.memory
@@ -8,6 +8,7 @@ import type {
8
8
  import type { RuntimeExtensionConfig } from "./kernel/extensions";
9
9
  import type { RuntimeDegradation } from "./kernel/degradation";
10
10
  import type { PiToolCandidate } from "./pi/tool/compiler";
11
+ import { validateToolArguments } from "@earendil-works/pi-ai";
11
12
 
12
13
  /** Tool Surface 筛选策略(不含 Manifest deny 列表)。 */
13
14
  export interface ToolSurfaceSelectionPolicy {
@@ -94,9 +95,15 @@ export function piCandidateToToolSpec(candidate: PiToolCandidate): ToolSpec {
94
95
  if (typeof execute !== "function") {
95
96
  throw new Error(`Tool "${name}" is not executable`);
96
97
  }
98
+ const validated = validateToolArguments(candidate.tool, {
99
+ type: "toolCall",
100
+ id: ctx.toolCallId,
101
+ name,
102
+ arguments: input as Record<string, unknown>,
103
+ });
97
104
  const result = await execute(
98
105
  ctx.toolCallId,
99
- input,
106
+ validated,
100
107
  ctx.signal,
101
108
  undefined,
102
109
  );