@springbrand/agent-runtime 0.2.0-alpha.13 → 0.2.0-alpha.15

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.
@@ -652,9 +652,14 @@ export function defineRuntimeAgent<
652
652
  const policy = assembly.surfacePolicy;
653
653
  return {
654
654
  ...assembly,
655
- bindings: assembly.bindings?.workspace
656
- ? { workspace: assembly.bindings.workspace }
657
- : {},
655
+ bindings: {
656
+ ...(assembly.bindings?.workspace
657
+ ? { workspace: assembly.bindings.workspace }
658
+ : {}),
659
+ ...(assembly.bindings?.codeExecution
660
+ ? { codeExecution: assembly.bindings.codeExecution }
661
+ : {}),
662
+ },
658
663
  memoryProfile: {
659
664
  enabled: false,
660
665
  memoryTokens: 2_000,
@@ -1,6 +1,5 @@
1
1
  import type { SkillSource } from "agents/skills";
2
2
  import type {
3
- RuntimeCodeExecutionPort,
4
3
  RuntimeGatewayPort,
5
4
  RuntimeMemoryPort,
6
5
  RuntimePlatformPort,
@@ -44,7 +43,11 @@ import type {
44
43
  RuntimeSkillContribution,
45
44
  RuntimeToolSurfacePolicy,
46
45
  } from "./runtime-definition";
47
- import type { ToolRegistry } from "./tool-registry";
46
+ import {
47
+ toolRegistryFromPiCandidates,
48
+ type RuntimeCodeExecutionFactory,
49
+ type ToolRegistry,
50
+ } from "./tool-registry";
48
51
  import { withRuntimeLoadTimeout } from "./kernel/runtime-load";
49
52
  import {
50
53
  basePiToolCandidates,
@@ -52,6 +55,7 @@ import {
52
55
  } from "./pi/tool/base";
53
56
  import {
54
57
  browserQuickActionPiToolCandidates,
58
+ codeExecutionPiToolCandidate,
55
59
  } from "./pi/tool/core";
56
60
  import { skillPiToolCandidates } from "./pi/tool/skill";
57
61
  import { createWebSearch } from "./pi/tool/web-search";
@@ -77,7 +81,7 @@ export interface RuntimeAssemblyInput {
77
81
  readonly provider: RuntimeProviderPort;
78
82
  readonly platform: RuntimePlatformPort;
79
83
  readonly workspace?: WorkspacePort;
80
- readonly codeExecution?: RuntimeCodeExecutionPort;
84
+ readonly codeExecution?: RuntimeCodeExecutionFactory;
81
85
  readonly sandbox?: RuntimeSandboxPort;
82
86
  readonly memory?: RuntimeMemoryPort;
83
87
  readonly hostTools: readonly PiToolCandidate[];
@@ -150,6 +154,7 @@ function assertMemoryProfile(profile: RuntimeMemoryProfile): void {
150
154
  }
151
155
 
152
156
  interface ToolSurfaceInput {
157
+ readonly codeExecution?: RuntimeCodeExecutionFactory;
153
158
  readonly platform: RuntimePlatformPort;
154
159
  readonly workspace?: WorkspacePort;
155
160
  readonly memory?: {
@@ -215,18 +220,46 @@ async function createToolSurface(
215
220
  ...staticCandidates,
216
221
  ...candidates,
217
222
  ]) {
218
- const name = requiredName(candidate.tool.name, "Pi Tool");
223
+ const name = requiredName(candidate.tool.name, "SpringBrand Tool");
219
224
  if (!visible(candidate)) continue;
220
225
  if (!EXECUTION_LEVELS.includes(candidate.requiredExecutionLevel)) {
221
- throw new Error(`Pi Tool ${name} execution level is invalid`);
226
+ throw new Error(`SpringBrand Tool ${name} execution level is invalid`);
222
227
  }
223
228
  if (tools.has(name)) {
224
- throw new Error(`Duplicate Runtime Pi Tool: ${name}`);
229
+ throw new Error(`Duplicate Runtime SpringBrand Tool: ${name}`);
225
230
  }
226
231
  tools.set(name, Object.freeze({ ...candidate }));
227
232
  }
228
233
 
229
- return Object.freeze([...tools.values()]);
234
+ const finalized = [...tools.values()];
235
+ if (tools.has("execute")) {
236
+ throw new Error("SpringBrand reserved Runtime Tool name: execute");
237
+ }
238
+ if (
239
+ !input.codeExecution ||
240
+ deny.has("execute") ||
241
+ allowsTool?.("execute") === false
242
+ ) {
243
+ return Object.freeze(finalized);
244
+ }
245
+ const mergeable = finalized.filter(
246
+ (candidate) =>
247
+ !candidate.interaction &&
248
+ typeof candidate.tool.execute === "function",
249
+ );
250
+ const direct = finalized.filter(
251
+ (candidate) =>
252
+ candidate.interaction ||
253
+ typeof candidate.tool.execute !== "function",
254
+ );
255
+ return Object.freeze([
256
+ Object.freeze(codeExecutionPiToolCandidate(
257
+ input.codeExecution.create(
258
+ toolRegistryFromPiCandidates(mergeable),
259
+ ),
260
+ )),
261
+ ...direct,
262
+ ]);
230
263
  },
231
264
  }),
232
265
  extensions: input.extensions.filter(
@@ -246,7 +279,7 @@ class RuntimeBuilder {
246
279
  private platform?: RuntimePlatformPort;
247
280
  private gateway?: RuntimeGatewayPort;
248
281
  private workspace?: WorkspacePort;
249
- private codeExecution?: RuntimeCodeExecutionPort;
282
+ private codeExecution?: RuntimeCodeExecutionFactory;
250
283
  private sandbox?: RuntimeSandboxPort;
251
284
  private schedule?: RuntimeSchedulePort;
252
285
  private memoryProfile: RuntimeMemoryProfile = DISABLED_MEMORY;
@@ -470,6 +503,7 @@ class RuntimeBuilder {
470
503
  });
471
504
  const skillSources = [...this.skillSources.values()];
472
505
  const toolSurface = await createToolSurface({
506
+ ...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
473
507
  platform: this.platform,
474
508
  ...(this.workspace ? { workspace: this.workspace } : {}),
475
509
  ...(this.memoryProfile.enabled && this.memory
@@ -766,6 +800,9 @@ export async function assembleRuntimeSnapshot<
766
800
  ...(toolAssembly.bindings?.workspace
767
801
  ? { workspace: toolAssembly.bindings.workspace }
768
802
  : {}),
803
+ ...(toolAssembly.bindings?.codeExecution
804
+ ? { codeExecution: toolAssembly.bindings.codeExecution }
805
+ : {}),
769
806
  ...(memoryProfile.enabled && toolAssembly.bindings?.memory
770
807
  ? { memory: toolAssembly.bindings.memory }
771
808
  : {}),
package/src/runtime.ts CHANGED
@@ -187,6 +187,11 @@ interface ActiveTurn {
187
187
  agent: PreparedPiTurnAdapter;
188
188
  }
189
189
 
190
+ interface PlannedContinuationData {
191
+ readonly submissionId: string;
192
+ readonly requestId: string;
193
+ }
194
+
190
195
  // 作用:把任意异常整理成可以持久化或发给客户端的文字。
191
196
  // 调用:Turn 执行、恢复和业务投影捕获 `unknown` 异常时调用。
192
197
  // 原因:错误边界不能假定抛出值一定是 `Error`。
@@ -553,7 +558,7 @@ export abstract class AgentRuntimeKernel<
553
558
  status: "failed" as const,
554
559
  error:
555
560
  receipt.error ??
556
- `Pi recovery ended with status ${receipt.status}`,
561
+ `SpringBrand recovery ended with status ${receipt.status}`,
557
562
  };
558
563
  } catch (error) {
559
564
  if (
@@ -651,7 +656,7 @@ export abstract class AgentRuntimeKernel<
651
656
  if (contested && !parked) {
652
657
  await closeRuntimeGatewaySession(gatewaySession, "gateway.close.contested");
653
658
  throw new Error(
654
- "Cannot reload Runtime while a revision-pinned Pi Turn is active",
659
+ "Cannot reload Runtime while a revision-pinned SpringBrand Turn is active",
655
660
  );
656
661
  }
657
662
  const repin = parked
@@ -1186,7 +1191,7 @@ export abstract class AgentRuntimeKernel<
1186
1191
  private preparedPi(): PreparedPiRuntime {
1187
1192
  if (!this.runtimePi) {
1188
1193
  throw new Error(
1189
- "Pi Runtime must be prepared before starting a Turn",
1194
+ "SpringBrand Runtime must be prepared before starting a Turn",
1190
1195
  );
1191
1196
  }
1192
1197
  return this.runtimePi;
@@ -1695,7 +1700,7 @@ export abstract class AgentRuntimeKernel<
1695
1700
  );
1696
1701
  const submission = this.readSubmission(submissionId);
1697
1702
  if (!submission) {
1698
- throw new Error(`Unknown Pi submission: ${submissionId}`);
1703
+ throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
1699
1704
  }
1700
1705
  return submission;
1701
1706
  }
@@ -1890,7 +1895,7 @@ export abstract class AgentRuntimeKernel<
1890
1895
  ): void {
1891
1896
  const submission = this.readSubmission(submissionId);
1892
1897
  if (!submission) {
1893
- throw new Error(`Unknown Pi submission: ${submissionId}`);
1898
+ throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
1894
1899
  }
1895
1900
  const existing = this.db.settlements.find(submissionId, call.toolCallId);
1896
1901
  const args = json(call.args);
@@ -2038,7 +2043,7 @@ export abstract class AgentRuntimeKernel<
2038
2043
  const latest = this.readSubmission(submission.submissionId);
2039
2044
  if (!latest) {
2040
2045
  throw new Error(
2041
- `Unknown Pi submission: ${submission.submissionId}`,
2046
+ `Unknown SpringBrand submission: ${submission.submissionId}`,
2042
2047
  );
2043
2048
  }
2044
2049
  if (isTerminalSubmissionStatus(latest.status)) return latest;
@@ -2101,7 +2106,7 @@ export abstract class AgentRuntimeKernel<
2101
2106
  ) {
2102
2107
  if (terminal.completedAt == null) {
2103
2108
  throw new Error(
2104
- `Aborted Pi submission has no completion time: ${latest.submissionId}`,
2109
+ `Aborted SpringBrand submission has no completion time: ${latest.submissionId}`,
2105
2110
  );
2106
2111
  }
2107
2112
  this.transcript.appendTurnAbortedMarker(
@@ -2224,7 +2229,7 @@ export abstract class AgentRuntimeKernel<
2224
2229
  }
2225
2230
  }
2226
2231
  }
2227
- throw new Error("Pi recovery plan did not converge");
2232
+ throw new Error("SpringBrand recovery plan did not converge");
2228
2233
  }
2229
2234
 
2230
2235
  // #endregion
@@ -2253,7 +2258,7 @@ export abstract class AgentRuntimeKernel<
2253
2258
  typeof userMessage.timestamp !== "number" ||
2254
2259
  !submission.userMessageId
2255
2260
  ) {
2256
- throw new Error("Queued Pi user message is invalid");
2261
+ throw new Error("Queued SpringBrand user message is invalid");
2257
2262
  }
2258
2263
  const uiMessage = submission.queuedUiMessageJson
2259
2264
  ? JSON.parse(submission.queuedUiMessageJson) as UIMessage & {
@@ -2282,7 +2287,7 @@ export abstract class AgentRuntimeKernel<
2282
2287
  if (!latest || latest.status !== "pending") {
2283
2288
  if (!latest) {
2284
2289
  throw new Error(
2285
- `Unknown Pi submission: ${submission.submissionId}`,
2290
+ `Unknown SpringBrand submission: ${submission.submissionId}`,
2286
2291
  );
2287
2292
  }
2288
2293
  return latest;
@@ -2290,7 +2295,7 @@ export abstract class AgentRuntimeKernel<
2290
2295
  const running = this.db.submissions.findRunning();
2291
2296
  if (running) {
2292
2297
  throw new Error(
2293
- `Pi submission ${running.submissionId} is already running`,
2298
+ `SpringBrand submission ${running.submissionId} is already running`,
2294
2299
  );
2295
2300
  }
2296
2301
  if (submission.regenerateMessageId) {
@@ -2307,7 +2312,7 @@ export abstract class AgentRuntimeKernel<
2307
2312
  );
2308
2313
  if (!appended) {
2309
2314
  throw new Error(
2310
- `Pi Session message ${submission.userMessageId} already exists`,
2315
+ `SpringBrand Session message ${submission.userMessageId} already exists`,
2311
2316
  );
2312
2317
  }
2313
2318
  }
@@ -2317,7 +2322,7 @@ export abstract class AgentRuntimeKernel<
2317
2322
  ["pending"],
2318
2323
  )) {
2319
2324
  throw new Error(
2320
- `Pi submission ${submission.submissionId} could not activate`,
2325
+ `SpringBrand submission ${submission.submissionId} could not activate`,
2321
2326
  );
2322
2327
  }
2323
2328
  this.db.submissions.clearQueuedPayload(
@@ -2342,7 +2347,7 @@ export abstract class AgentRuntimeKernel<
2342
2347
  ): Promise<StoredSubmission> {
2343
2348
  const submission = this.readSubmission(submissionId);
2344
2349
  if (!submission) {
2345
- throw new Error(`Unknown Pi submission: ${submissionId}`);
2350
+ throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
2346
2351
  }
2347
2352
  if (isTerminalSubmissionStatus(submission.status)) {
2348
2353
  return submission;
@@ -2385,7 +2390,7 @@ export abstract class AgentRuntimeKernel<
2385
2390
  this.sendChatTerminal(
2386
2391
  submission.requestId,
2387
2392
  failed.status === "error"
2388
- ? failed.error ?? "Pi turn failed"
2393
+ ? failed.error ?? "SpringBrand turn failed"
2389
2394
  : undefined,
2390
2395
  recovery,
2391
2396
  );
@@ -2455,7 +2460,7 @@ export abstract class AgentRuntimeKernel<
2455
2460
 
2456
2461
  const ready = this.readSubmission(submissionId);
2457
2462
  if (!ready) {
2458
- throw new Error(`Unknown Pi submission: ${submissionId}`);
2463
+ throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
2459
2464
  }
2460
2465
  if (isTerminalSubmissionStatus(ready.status)) return ready;
2461
2466
  if (ready.abortReason) {
@@ -2517,6 +2522,7 @@ export abstract class AgentRuntimeKernel<
2517
2522
  agent: adapter,
2518
2523
  };
2519
2524
  let streamId: string | undefined;
2525
+ let runResult: import("./pi/runtime-adapter").PiTurnRunResult | undefined;
2520
2526
 
2521
2527
  const deactivate = this.submissions.activate(submission, turn);
2522
2528
  await this.broadcastApprovals();
@@ -2536,7 +2542,7 @@ export abstract class AgentRuntimeKernel<
2536
2542
  streamId = activeStreamId;
2537
2543
  this.streamBySubmission.set(submissionId, streamId);
2538
2544
  try {
2539
- await adapter.run({});
2545
+ runResult = await adapter.run({});
2540
2546
  } finally {
2541
2547
  this.streamBySubmission.delete(submissionId);
2542
2548
  }
@@ -2548,10 +2554,27 @@ export abstract class AgentRuntimeKernel<
2548
2554
  if (streamId) this.failRecoverableStream(streamId);
2549
2555
  return this.readSubmission(submissionId)!;
2550
2556
  }
2557
+ if (runResult?.kind === "yielded") {
2558
+ const latest = this.readSubmission(submissionId)!;
2559
+ if (latest.abortReason) {
2560
+ return this.submissions.finish(
2561
+ latest,
2562
+ "aborted",
2563
+ latest.abortReason,
2564
+ );
2565
+ }
2566
+ await this.schedule(
2567
+ 0,
2568
+ "_piPlannedContinuation",
2569
+ { submissionId, requestId: submission.requestId },
2570
+ { idempotent: true },
2571
+ );
2572
+ return latest;
2573
+ }
2551
2574
  const intent = terminalIntent ?? {
2552
2575
  outcome: "failed" as const,
2553
2576
  message:
2554
- "Pi turn ended without an authoritative assistant message",
2577
+ "SpringBrand turn ended without an authoritative assistant message",
2555
2578
  };
2556
2579
  const terminal = await this.submissions.finish(
2557
2580
  submission,
@@ -2713,7 +2736,7 @@ export abstract class AgentRuntimeKernel<
2713
2736
  this.sendChatTerminal(
2714
2737
  turn.requestId,
2715
2738
  submission.status === "error"
2716
- ? submission.error ?? "Pi turn failed"
2739
+ ? submission.error ?? "SpringBrand turn failed"
2717
2740
  : undefined,
2718
2741
  turn.continuation,
2719
2742
  );
@@ -3562,9 +3585,9 @@ export abstract class AgentRuntimeKernel<
3562
3585
  const adapter = this.createSubmissionExecutionAdapter(submission);
3563
3586
  const spec = adapter.interactionSpec(pending.toolName);
3564
3587
  if (!spec) return { ok: false };
3565
- if (!spec.validateResponse(response)) return { ok: false };
3566
3588
 
3567
3589
  const input = safeParseJson(pending.inputJson);
3590
+ if (!spec.validateResponse(input, response)) return { ok: false };
3568
3591
  const settled = spec.settle
3569
3592
  ? spec.settle(input, response)
3570
3593
  : defaultInteractionResult(response);
@@ -3687,6 +3710,21 @@ export abstract class AgentRuntimeKernel<
3687
3710
  }
3688
3711
  }
3689
3712
 
3713
+ async _piPlannedContinuation(
3714
+ data?: PlannedContinuationData,
3715
+ ): Promise<void> {
3716
+ if (!data?.submissionId || !data.requestId) return;
3717
+ const submission = this.readSubmission(data.submissionId);
3718
+ if (
3719
+ !submission ||
3720
+ submission.requestId !== data.requestId ||
3721
+ isTerminalSubmissionStatus(submission.status)
3722
+ ) {
3723
+ return;
3724
+ }
3725
+ await this.submissions.recoverAfterCurrent(submission.submissionId);
3726
+ }
3727
+
3690
3728
  // 作用:Agent Tool 子运行开始后重算一次活动投影。
3691
3729
  // 调用:Agents SDK 在登记子运行后调用。
3692
3730
  // 原因:分离的子运行不进 Submission 表,不在这里重算,Host 的列表会显示成已经空闲。
@@ -1,5 +1,6 @@
1
1
  import type { ExecutionLevel } from "./lib/execution-level";
2
2
  import type {
3
+ RuntimeCodeExecutionPort,
3
4
  RuntimeMemoryPort,
4
5
  RuntimeSubagentPort,
5
6
  WorkspacePort,
@@ -37,6 +38,11 @@ export interface ToolSpec {
37
38
  /** 一次装配声明的全部 Tool,键即模型可见的工具名。 */
38
39
  export type ToolRegistry = Record<string, ToolSpec>;
39
40
 
41
+ /** 延迟到最终 Tool Surface 完成后再创建 Code Mode 执行能力。 */
42
+ export interface RuntimeCodeExecutionFactory {
43
+ create(tools: ToolRegistry): RuntimeCodeExecutionPort;
44
+ }
45
+
40
46
  export function emptyToolRegistry(): ToolRegistry {
41
47
  return {};
42
48
  }
@@ -66,7 +72,7 @@ export function toolRegistryFromPiCandidates(
66
72
  for (const candidate of list) {
67
73
  const name = candidate.tool.name;
68
74
  if (!name.trim()) {
69
- throw new Error("Pi Tool name must not be empty");
75
+ throw new Error("SpringBrand Tool name must not be empty");
70
76
  }
71
77
  registry[name] = piCandidateToToolSpec(candidate);
72
78
  }
@@ -102,6 +108,7 @@ export function piCandidateToToolSpec(candidate: PiToolCandidate): ToolSpec {
102
108
  /** Kernel 绑定仍需要的执行后端;不出现在 Definition 公开类型里。 */
103
109
  export interface RuntimeToolBindings {
104
110
  readonly workspace?: WorkspacePort;
111
+ readonly codeExecution?: RuntimeCodeExecutionFactory;
105
112
  readonly memory?: RuntimeMemoryPort;
106
113
  readonly subagents?: RuntimeSubagentPort;
107
114
  }