@springbrand/agent-runtime 0.2.0-alpha.41 → 0.2.0-alpha.43

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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/adapter/cloudflare/resources/r2-skill-source.ts +215 -0
  3. package/src/adapter/cloudflare/resources/runtime-resources.ts +1 -18
  4. package/src/adapter/cloudflare/subagent/tools.ts +2 -5
  5. package/src/adapter/cloudflare/universal-agent/preparation.ts +21 -9
  6. package/src/adapter/cloudflare/universal-agent/tools.ts +3 -2
  7. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +15 -0
  8. package/src/index.ts +3 -0
  9. package/src/kernel/bindings.ts +38 -6
  10. package/src/layers/context/budget/gate.ts +3 -3
  11. package/src/layers/orchestration/temporary-agent/workspace.ts +2 -0
  12. package/src/lib/prompt.ts +30 -14
  13. package/src/pi/assembly/snapshot.ts +7 -1
  14. package/src/pi/runtime-adapter/assembly.ts +8 -3
  15. package/src/pi/runtime-adapter/execution.ts +108 -13
  16. package/src/pi/runtime-adapter/models.ts +9 -6
  17. package/src/pi/runtime-adapter/openrouter-messages.ts +10 -3
  18. package/src/pi/tool/base.ts +42 -14
  19. package/src/pi/tool/compiler.ts +15 -2
  20. package/src/pi/tool/core-host.ts +228 -1
  21. package/src/pi/tool/core.ts +37 -5
  22. package/src/pi/tool/declared.ts +3 -0
  23. package/src/pi/tool/nested-tools.ts +5 -1
  24. package/src/pi/tool/schedule.ts +12 -10
  25. package/src/pi/tool/skill.ts +240 -86
  26. package/src/pi/tool/subagent.ts +2 -0
  27. package/src/pi/tool/time.ts +1 -1
  28. package/src/pi/tool/web-fetch.ts +1 -1
  29. package/src/pi/tool/web-search/web-search.ts +2 -1
  30. package/src/pi/tool/workspace-revision.ts +2 -1
  31. package/src/pi/tool/workspace-sandbox.ts +10 -21
  32. package/src/runtime-agent.ts +3 -0
  33. package/src/runtime-assembler.ts +105 -7
  34. package/src/runtime-definition.ts +1 -0
  35. package/src/runtime.ts +50 -0
@@ -30,11 +30,15 @@ import {
30
30
  } from "./lib/execution-level";
31
31
  import {
32
32
  createPiRuntimeAssembly,
33
+ type FinalizedPiToolSurface,
33
34
  type PiToolSurface,
34
35
  type PiRuntimeAssembly,
35
36
  } from "./pi/assembly/snapshot";
36
37
  import type { AgentTool } from "@earendil-works/pi-agent-core";
37
- import type { PiToolCandidate } from "./pi/tool/compiler";
38
+ import type {
39
+ PiToolCandidate,
40
+ ToolExposureMode,
41
+ } from "./pi/tool/compiler";
38
42
  import type {
39
43
  AssembleRuntimeSnapshotInput,
40
44
  RuntimeSettings,
@@ -43,6 +47,7 @@ import type {
43
47
  RuntimeSkillContribution,
44
48
  RuntimeToolSurfacePolicy,
45
49
  } from "./runtime-definition";
50
+ import type { RuntimeCodeExecutionFactory } from "./kernel/bindings";
46
51
  import {
47
52
  basePiToolCandidates,
48
53
  memoryPiToolCandidate,
@@ -50,6 +55,7 @@ import {
50
55
  import {
51
56
  BROWSER_EXECUTE_TOOL_NAME,
52
57
  browserExecutionPiToolCandidate,
58
+ codeExecutionPiToolCandidate,
53
59
  } from "./pi/tool/core";
54
60
  import { skillPiToolCandidates } from "./pi/tool/skill";
55
61
  import { createWebSearch } from "./pi/tool/web-search";
@@ -80,6 +86,7 @@ export interface RuntimeAssemblyInput {
80
86
  readonly provider: RuntimeProviderPort;
81
87
  readonly platform: RuntimePlatformPort;
82
88
  readonly workspace?: WorkspacePort;
89
+ readonly codeExecution?: RuntimeCodeExecutionFactory;
83
90
  readonly sandbox?: RuntimeSandboxPort;
84
91
  readonly memory?: RuntimeMemoryPort;
85
92
  readonly hostTools: readonly PiToolCandidate[];
@@ -153,6 +160,7 @@ function assertMemoryProfile(profile: RuntimeMemoryProfile): void {
153
160
  }
154
161
 
155
162
  interface ToolSurfaceInput {
163
+ readonly codeExecution?: RuntimeCodeExecutionFactory;
156
164
  readonly platform: RuntimePlatformPort;
157
165
  readonly workspace?: WorkspacePort;
158
166
  readonly memory?: {
@@ -173,6 +181,10 @@ interface ToolSurface {
173
181
  readonly degradations: readonly RuntimeDegradation[];
174
182
  }
175
183
 
184
+ function exposureOf(candidate: PiToolCandidate): ToolExposureMode {
185
+ return candidate.exposureMode ?? "both";
186
+ }
187
+
176
188
  // 唯一 Tool Surface seam:只读取已配置 Port、Resource 和 Agent policy,
177
189
  // 统一生成可见 Tool,并在一处做名称、执行档位与冲突校验。
178
190
  async function createToolSurface(
@@ -198,10 +210,15 @@ async function createToolSurface(
198
210
  });
199
211
  // 平台没有浏览器能力时注册空集:宁可没有这个 Tool,也不注册一个必然失败的 Tool 误导模型。
200
212
  // `create()` 推迟到确认这个名字真的可见之后,被 deny 的装配不白建一个浏览器连接器。
213
+ // `direct` exposure 使它只走 Direct 调用,不再被并进 `execute` 的工具集——两个 Code Mode 类工具互相嵌套
214
+ // 会让「哪次执行被记录、被重放」变得无法解释,而它们的分工本就由 System Prompt 划清。
201
215
  const browserVisible = !deny.has(BROWSER_EXECUTE_TOOL_NAME) &&
202
216
  allowsTool?.(BROWSER_EXECUTE_TOOL_NAME) !== false;
203
217
  const browserCandidates = input.platform.browser && browserVisible
204
- ? [browserExecutionPiToolCandidate(input.platform.browser.create())]
218
+ ? [{
219
+ ...browserExecutionPiToolCandidate(input.platform.browser.create()),
220
+ exposureMode: "direct" as const,
221
+ }]
205
222
  : [];
206
223
  const baseCandidates = basePiToolCandidates(input.webSearch);
207
224
  const memoryCandidates = input.memory
@@ -238,16 +255,87 @@ async function createToolSurface(
238
255
  if (!EXECUTION_LEVELS.includes(candidate.requiredExecutionLevel)) {
239
256
  throw new Error(`SpringBrand Tool ${name} execution level is invalid`);
240
257
  }
241
- if (name === "execute") {
242
- throw new Error("SpringBrand reserved Runtime Tool name: execute");
243
- }
244
258
  if (tools.has(name)) {
245
259
  throw new Error(`Duplicate Runtime SpringBrand Tool: ${name}`);
246
260
  }
247
- tools.set(name, classify(candidate));
261
+ tools.set(name, Object.freeze({ ...candidate }));
248
262
  }
249
263
 
250
- return Object.freeze([...tools.values()]);
264
+ const finalized = [...tools.values()];
265
+ const directVisible = finalized.filter(
266
+ (candidate) => exposureOf(candidate) !== "codemode",
267
+ );
268
+ if (tools.has("execute")) {
269
+ throw new Error("SpringBrand reserved Runtime Tool name: execute");
270
+ }
271
+ if (
272
+ !input.codeExecution ||
273
+ deny.has("execute") ||
274
+ allowsTool?.("execute") === false
275
+ ) {
276
+ return Object.freeze({
277
+ candidates: Object.freeze(directVisible.map(classify)),
278
+ codeExecutionCandidates: Object.freeze([]),
279
+ }) satisfies FinalizedPiToolSurface;
280
+ }
281
+ const mergeable = finalized.filter(
282
+ (candidate) =>
283
+ exposureOf(candidate) !== "direct" &&
284
+ !candidate.interaction &&
285
+ typeof candidate.tool.execute === "function",
286
+ );
287
+ const codeExecutionCandidates = Object.freeze([...mergeable]);
288
+ const codeExecutionToolNames = new Set(
289
+ codeExecutionCandidates.map(({ tool }) => tool.name),
290
+ );
291
+ const codeExecution = input.codeExecution.create(
292
+ codeExecutionCandidates,
293
+ );
294
+ const topLevelOnly = directVisible.filter(
295
+ ({ tool }) => !codeExecutionToolNames.has(tool.name),
296
+ );
297
+ const routeList = (deferred: boolean) => {
298
+ const matches = topLevelOnly.filter((candidate) =>
299
+ (defersTool?.(candidate.tool.name) === true) === deferred
300
+ );
301
+ return matches.length > 0
302
+ ? matches.map(({ tool }) =>
303
+ `- \`${tool.name}\` — ${(tool.label ?? tool.name)
304
+ .replaceAll(/\s+/g, " ").trim().replaceAll("`", "'")}`
305
+ ).join("\n")
306
+ : "- None.";
307
+ };
308
+ const routeGuidance = [
309
+ "## Tool routing",
310
+ "Top-level deferred Tools are not callable through `tools.*`. Use Provider Tool Search with their exact name, then call them directly.",
311
+ "Top-level immediate Tools are already available outside execute; call them directly.",
312
+ "",
313
+ "## Top-level deferred Tools",
314
+ routeList(true),
315
+ "",
316
+ "## Top-level immediate Tools",
317
+ routeList(false),
318
+ "",
319
+ ].join("\n");
320
+ const materializeGuidance = codeExecutionCandidates.some(
321
+ ({ tool }) => tool.name === "materialize_skill_resource",
322
+ )
323
+ ? "Skill resource copying is available only inside execute through " +
324
+ "`tools.materialize_skill_resource({ name, path, destination })`; " +
325
+ "no Direct Tool is registered for it. " +
326
+ "Copy multiple resources sequentially in one execute so durable replay preserves call order.\n\n"
327
+ : "";
328
+ return Object.freeze({
329
+ candidates: Object.freeze([
330
+ classify(codeExecutionPiToolCandidate({
331
+ ...codeExecution,
332
+ description:
333
+ routeGuidance + "\n" + materializeGuidance + codeExecution.description,
334
+ })),
335
+ ...directVisible.map(classify),
336
+ ]),
337
+ codeExecutionCandidates,
338
+ }) satisfies FinalizedPiToolSurface;
251
339
  },
252
340
  }),
253
341
  extensions: input.extensions.filter(
@@ -275,6 +363,7 @@ class RuntimeBuilder {
275
363
  private platform?: RuntimePlatformPort;
276
364
  private gateway?: RuntimeGatewayPort;
277
365
  private workspace?: WorkspacePort;
366
+ private codeExecution?: RuntimeCodeExecutionFactory;
278
367
  private sandbox?: RuntimeSandboxPort;
279
368
  private schedule?: RuntimeSchedulePort;
280
369
  private memoryProfile: RuntimeMemoryProfile = DISABLED_MEMORY;
@@ -310,6 +399,7 @@ class RuntimeBuilder {
310
399
  this.provider = input.provider;
311
400
  this.platform = input.platform;
312
401
  this.workspace = input.workspace;
402
+ this.codeExecution = input.codeExecution;
313
403
  this.sandbox = input.sandbox;
314
404
  this.memoryProfile = input.memoryProfile;
315
405
  this.memory = input.memory;
@@ -449,6 +539,9 @@ class RuntimeBuilder {
449
539
  if (!this.platform.loader) {
450
540
  throw new Error("Runtime platform loader is required");
451
541
  }
542
+ if (typeof this.platform.outbound !== "function") {
543
+ throw new Error("Runtime platform outbound port is required");
544
+ }
452
545
 
453
546
  assertMemoryProfile(this.memoryProfile);
454
547
  if (this.memoryProfile.enabled && (!this.workspace || !this.memory)) {
@@ -498,6 +591,7 @@ class RuntimeBuilder {
498
591
  });
499
592
  const skillSources = [...this.skillSources.values()];
500
593
  const toolSurface = await createToolSurface({
594
+ ...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
501
595
  platform: this.platform,
502
596
  ...(this.workspace ? { workspace: this.workspace } : {}),
503
597
  ...(this.memoryProfile.enabled && this.memory
@@ -522,6 +616,7 @@ class RuntimeBuilder {
522
616
  const bindings: RuntimeBindings = Object.freeze({
523
617
  provider,
524
618
  platform: Object.freeze({ ...this.platform }),
619
+ ...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
525
620
  ...(this.gateway ? { gateway: this.gateway } : {}),
526
621
  ...(this.workspace ? { workspace: this.workspace } : {}),
527
622
  ...(this.memoryProfile.enabled && this.memory
@@ -666,6 +761,9 @@ export async function assembleRuntimeSnapshot<
666
761
  ...(toolAssembly.bindings?.workspace
667
762
  ? { workspace: toolAssembly.bindings.workspace }
668
763
  : {}),
764
+ ...(toolAssembly.bindings?.codeExecution
765
+ ? { codeExecution: toolAssembly.bindings.codeExecution }
766
+ : {}),
669
767
  ...(memoryProfile.enabled && toolAssembly.bindings?.memory
670
768
  ? { memory: toolAssembly.bindings.memory }
671
769
  : {}),
@@ -33,6 +33,7 @@ export interface ToolSurfaceSelectionPolicy {
33
33
  /** Kernel 绑定仍需要的执行后端;不出现在 Definition 公开类型里。 */
34
34
  export interface RuntimeToolBindings {
35
35
  readonly workspace?: import("./kernel/bindings").WorkspacePort;
36
+ readonly codeExecution?: import("./kernel/bindings").RuntimeCodeExecutionFactory;
36
37
  readonly memory?: import("./kernel/bindings").RuntimeMemoryPort;
37
38
  readonly subagents?: import("./kernel/bindings").RuntimeSubagentPort;
38
39
  }
package/src/runtime.ts CHANGED
@@ -899,6 +899,7 @@ export abstract class AgentRuntimeKernel<
899
899
  return this.pi.createTurn({
900
900
  prepared: this.preparedPi(),
901
901
  pinnedDescriptor: submission.assemblyDescriptor,
902
+ modelSessionId: this.name,
902
903
  submission: {
903
904
  id: submission.submissionId,
904
905
  requestId: submission.requestId,
@@ -975,6 +976,29 @@ export abstract class AgentRuntimeKernel<
975
976
  occurredAt: event.timestamp,
976
977
  });
977
978
  },
979
+ onNestedToolStarted: (event) => {
980
+ this.telemetry.capture("toolStarted", {
981
+ submissionId: submission.submissionId,
982
+ ...event,
983
+ });
984
+ },
985
+ onNestedToolFinished: (event) => {
986
+ this.telemetry.capture("toolFinished", {
987
+ submissionId: submission.submissionId,
988
+ parentToolCallId: event.parentToolCallId,
989
+ toolCallId: event.toolCallId,
990
+ toolName: event.toolName,
991
+ outcome: event.outcome,
992
+ durationMs: event.durationMs,
993
+ ...(event.output ? {
994
+ output: event.output,
995
+ outputBytes: json(event.output).length,
996
+ } : {}),
997
+ ...(event.error ? { error: errorText(event.error) } : {}),
998
+ occurredAt: event.occurredAt,
999
+ });
1000
+ this.projectNestedToolSettlement(submission.submissionId, event);
1001
+ },
978
1002
  ...(toolExecutors && Object.keys(toolExecutors).length > 0
979
1003
  ? { toolExecutors }
980
1004
  : {}),
@@ -2178,6 +2202,32 @@ export abstract class AgentRuntimeKernel<
2178
2202
  return this.drainRuntimeEvents();
2179
2203
  }
2180
2204
 
2205
+ private projectNestedToolSettlement(
2206
+ submissionId: string,
2207
+ event: Readonly<{
2208
+ toolCallId: string;
2209
+ toolName: string;
2210
+ outcome: "completed" | "failed" | "cancelled";
2211
+ occurredAt: number;
2212
+ }>,
2213
+ ): void {
2214
+ const settlement: RuntimeToolSettlementEvent = {
2215
+ eventId: `${submissionId}:tool:${event.toolCallId}`,
2216
+ submissionId,
2217
+ toolCallId: event.toolCallId,
2218
+ toolName: event.toolName,
2219
+ status: event.outcome === "completed" ? "success" : "error",
2220
+ };
2221
+ this.db.transaction(() => {
2222
+ this.db.runtimeEvents.insert({
2223
+ eventId: settlement.eventId,
2224
+ body: json({ type: "tool-settlement", event: settlement }),
2225
+ createdAt: event.occurredAt,
2226
+ });
2227
+ });
2228
+ this.ctx.waitUntil(this.drainRuntimeEvents());
2229
+ }
2230
+
2181
2231
  // 终态事务用这个同步内核先补齐 ToolResult,再在同一事务末尾写 marker。
2182
2232
  private settleToolSync(
2183
2233
  submissionId: string,