@springbrand/agent-runtime 0.1.3-alpha.4 → 0.1.3-alpha.6

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 (45) hide show
  1. package/package.json +11 -3
  2. package/src/adapter/cloudflare/index.ts +55 -0
  3. package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
  4. package/src/adapter/cloudflare/sandbox/adapter.ts +1509 -0
  5. package/src/adapter/cloudflare/sandbox/id.ts +23 -0
  6. package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
  7. package/src/adapter/cloudflare/subagent/definition.ts +574 -0
  8. package/src/adapter/cloudflare/subagent/runner.ts +175 -0
  9. package/src/adapter/cloudflare/subagent/tools.ts +254 -0
  10. package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
  11. package/src/adapter/cloudflare/universal-agent/preparation.ts +273 -0
  12. package/src/adapter/cloudflare/universal-agent/tools.ts +74 -0
  13. package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
  14. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
  15. package/src/agent-tool-runtime.ts +152 -0
  16. package/src/index.ts +49 -7
  17. package/src/kernel/bindings.ts +6 -6
  18. package/src/kernel/recoverable-chat-agent.ts +12 -0
  19. package/src/kernel/runtime-load.ts +89 -0
  20. package/src/layers/orchestration/temporary-agent/core.ts +12 -1
  21. package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
  22. package/src/lib/mcp.ts +7 -3
  23. package/src/pi/assembly/context.ts +3 -3
  24. package/src/pi/assembly/extensions.ts +11 -22
  25. package/src/pi/assembly/snapshot.ts +1 -1
  26. package/src/pi/message/contract.ts +7 -0
  27. package/src/pi/message/conversion.ts +9 -1
  28. package/src/pi/runtime-adapter/assembly.ts +4 -10
  29. package/src/pi/runtime-adapter/index.ts +6 -2
  30. package/src/pi/tool/base.ts +17 -2
  31. package/src/pi/tool/compiler.ts +0 -1
  32. package/src/pi/tool/core.ts +13 -3
  33. package/src/pi/tool/mcp.ts +3 -4
  34. package/src/pi/tool/schedule.ts +11 -1
  35. package/src/pi/tool/skill.ts +55 -41
  36. package/src/pi/tool/subagent.ts +14 -2
  37. package/src/pi/tool/web-fetch.ts +0 -1
  38. package/src/pi/tool/web-search/web-search.ts +0 -1
  39. package/src/pi/tool/workspace-sandbox.ts +15 -7
  40. package/src/runtime-agent-context.ts +112 -0
  41. package/src/runtime-agent.ts +442 -328
  42. package/src/runtime-assembler.ts +255 -103
  43. package/src/runtime-definition.ts +173 -0
  44. package/src/runtime.ts +185 -25
  45. package/src/tool-registry.ts +143 -0
package/src/runtime.ts CHANGED
@@ -45,12 +45,18 @@ import type {
45
45
  RuntimeState,
46
46
  RuntimeTurnState,
47
47
  } from "./kernel/state";
48
- import { RuntimeLoadTracker } from "./kernel/runtime-load";
48
+ import {
49
+ RuntimeLoadTracker,
50
+ withRuntimeLoadTimeout,
51
+ } from "./kernel/runtime-load";
49
52
  import {
50
53
  prepareRuntimeCandidate,
51
54
  type RuntimeAssemblyInput,
55
+ type RuntimeCandidate,
52
56
  type RuntimeSnapshot,
53
57
  } from "./runtime-assembler";
58
+ import type { RuntimeAgentHooks } from "./runtime-definition";
59
+ import type { RuntimeTurnEventsPort } from "./kernel/bindings";
54
60
  import { connectConfiguredMcpServers } from "./lib/mcp";
55
61
  import { installConsoleSink } from "./lib/telemetry-dev";
56
62
  import {
@@ -62,6 +68,7 @@ import {
62
68
  type PiCanonicalTranscriptSnapshot,
63
69
  type PiChatRecoveryData,
64
70
  type UIChatRequestBody,
71
+ type RequestedCapability,
65
72
  type PiDurableMutation,
66
73
  type PiRecoveryCommand,
67
74
  type PiRecoveryDecision,
@@ -171,6 +178,16 @@ function json(value: unknown): string {
171
178
  return JSON.stringify(value);
172
179
  }
173
180
 
181
+ async function closeRuntimeGatewaySession(
182
+ session: RuntimeGatewaySession | undefined,
183
+ step: string,
184
+ ): Promise<void> {
185
+ if (!session) return;
186
+ await withRuntimeLoadTimeout(step, () => session.close()).catch(
187
+ () => undefined,
188
+ );
189
+ }
190
+
174
191
  function assistantUsageEvent(
175
192
  eventId: string,
176
193
  submissionId: string,
@@ -221,6 +238,44 @@ function userContentKey(message: PiCanonicalUserInput): string {
221
238
  );
222
239
  }
223
240
 
241
+ function requestedCapabilitiesOf(metadata: unknown): RequestedCapability[] {
242
+ if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
243
+ return [];
244
+ }
245
+ const record = metadata as Record<string, unknown>;
246
+ if (!("requestedCapabilities" in record)) return [];
247
+ if (!Array.isArray(record.requestedCapabilities)) {
248
+ throw new Error("requestedCapabilities must be an array");
249
+ }
250
+
251
+ const result: RequestedCapability[] = [];
252
+ const seen = new Set<string>();
253
+ for (const value of record.requestedCapabilities) {
254
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
255
+ throw new Error("requestedCapabilities contains an invalid capability");
256
+ }
257
+ const capability = value as Record<string, unknown>;
258
+ if (
259
+ (capability.kind !== "skill" && capability.kind !== "plan") ||
260
+ typeof capability.name !== "string" ||
261
+ !capability.name.trim() ||
262
+ typeof capability.label !== "string" ||
263
+ !capability.label.trim()
264
+ ) {
265
+ throw new Error("requestedCapabilities contains an invalid capability");
266
+ }
267
+ const key = `${capability.kind}:${capability.name}`;
268
+ if (seen.has(key)) continue;
269
+ seen.add(key);
270
+ result.push({
271
+ kind: capability.kind,
272
+ name: capability.name,
273
+ label: capability.label,
274
+ });
275
+ }
276
+ return result;
277
+ }
278
+
224
279
  interface PendingTemporaryAgentApproval {
225
280
  receipt: ApprovalReceipt;
226
281
  resolve(decision: TemporaryAgentApprovalDecision): void;
@@ -257,6 +312,8 @@ export abstract class AgentRuntimeKernel<
257
312
  private runtimeRevision?: string;
258
313
  private runtimePi?: PreparedPiRuntime;
259
314
  private runtimeGatewaySession?: RuntimeGatewaySession;
315
+ private runtimeTurnEvents?: RuntimeTurnEventsPort;
316
+ private runtimeHooks?: RuntimeAgentHooks;
260
317
  private piAdapter?: PiRuntimeAdapter;
261
318
  private readonly transcript: PiRuntimeTranscript;
262
319
  private readonly submissions: SubmissionLifecycle<
@@ -462,11 +519,21 @@ export abstract class AgentRuntimeKernel<
462
519
  // 调用:生成 Agent 在首次启动或显式重载 Runtime key 时调用。
463
520
  // 原因:先完整 prepare 再替换 Snapshot,且活跃 Turn 期间禁止换 revision,可避免半装配和恢复时能力漂移。
464
521
  protected async initConfig(input: RuntimeAssemblyInput): Promise<void> {
465
- const previous = this.runtimeSnapshot;
466
- this.runtimeLoad.advance("assembly");
467
522
  const candidate = await prepareRuntimeCandidate(input);
468
- const candidateSnapshot = candidate.snapshot;
523
+ await this.initCandidate(candidate);
524
+ }
525
+
526
+ private turnEventsPort(): RuntimeTurnEventsPort | undefined {
527
+ return this.runtimeTurnEvents ?? this.runtimeSnapshot?.bindings.turnEvents;
528
+ }
469
529
 
530
+ protected async initCandidate(candidate: RuntimeCandidate): Promise<void> {
531
+ this.runtimeTurnEvents =
532
+ candidate.turnEvents ?? candidate.snapshot.bindings.turnEvents;
533
+ this.runtimeHooks = candidate.hooks;
534
+ const previous = this.runtimeSnapshot;
535
+ const candidateSnapshot = candidate.snapshot;
536
+ this.runtimeLoad.advance("assembly");
470
537
  this.runtimeLoad.advance("mcp");
471
538
  await connectConfiguredMcpServers(
472
539
  this,
@@ -476,8 +543,15 @@ export abstract class AgentRuntimeKernel<
476
543
  let gatewaySession: RuntimeGatewaySession | undefined;
477
544
  if (candidateSnapshot.bindings.gateway) {
478
545
  try {
479
- gatewaySession = await candidateSnapshot.bindings.gateway.open(
480
- `${this.ctx.id.toString()}:${crypto.randomUUID()}`,
546
+ gatewaySession = await withRuntimeLoadTimeout(
547
+ "gateway.open",
548
+ () => candidateSnapshot.bindings.gateway!.open(
549
+ `${this.ctx.id.toString()}:${crypto.randomUUID()}`,
550
+ ),
551
+ {
552
+ onLateResult: (session) =>
553
+ closeRuntimeGatewaySession(session, "gateway.close.late"),
554
+ },
481
555
  );
482
556
  } catch {
483
557
  gatewayDegradations.push({
@@ -503,7 +577,7 @@ export abstract class AgentRuntimeKernel<
503
577
  ),
504
578
  });
505
579
  } catch (error) {
506
- await gatewaySession?.close().catch(() => undefined);
580
+ await closeRuntimeGatewaySession(gatewaySession, "gateway.close.failed");
507
581
  throw error;
508
582
  }
509
583
  const revision = await this.hash(prepared.revisionDescriptor);
@@ -515,18 +589,23 @@ export abstract class AgentRuntimeKernel<
515
589
  this.submissions.isBusy();
516
590
  const parked = contested && this.db.everyUnfinishedSubmissionParked();
517
591
  if (contested && !parked) {
518
- await gatewaySession?.close().catch(() => undefined);
592
+ await closeRuntimeGatewaySession(gatewaySession, "gateway.close.contested");
519
593
  throw new Error(
520
594
  "Cannot reload Runtime while a revision-pinned Pi Turn is active",
521
595
  );
522
596
  }
523
597
  const repin = parked
524
- ? await this.prepareParkedSubmissionRepin(prepared, revision)
598
+ ? await withRuntimeLoadTimeout(
599
+ "parked-submission.repin",
600
+ () => this.prepareParkedSubmissionRepin(prepared, revision),
601
+ )
525
602
  : undefined;
526
603
  let activated = false;
527
604
  const previousGatewaySession = this.runtimeGatewaySession;
528
605
  try {
529
- for (const guard of candidate.commitGuards) await guard();
606
+ for (const [index, guard] of candidate.commitGuards.entries()) {
607
+ await withRuntimeLoadTimeout(`commit-guard:${index}`, guard);
608
+ }
530
609
  if (candidateSnapshot.bindings.platform.telemetryConsole) {
531
610
  installConsoleSink();
532
611
  }
@@ -540,13 +619,19 @@ export abstract class AgentRuntimeKernel<
540
619
  } catch (error) {
541
620
  repin?.abort();
542
621
  if (activated && previous) this.pi.activate(previous);
543
- await gatewaySession?.close().catch(() => undefined);
622
+ await closeRuntimeGatewaySession(gatewaySession, "gateway.close.aborted");
544
623
  throw error;
545
624
  }
546
- await repin?.interrupt().catch((error) => {
547
- console.error("[runtime-repin:interrupt-failed]", error);
548
- });
549
- await previousGatewaySession?.close().catch(() => undefined);
625
+ if (repin) {
626
+ await withRuntimeLoadTimeout(
627
+ "parked-submission.interrupt",
628
+ () => repin.interrupt(),
629
+ ).catch(() => undefined);
630
+ }
631
+ await closeRuntimeGatewaySession(
632
+ previousGatewaySession,
633
+ "gateway.close.previous",
634
+ );
550
635
 
551
636
  const degradations = prepared.degradations;
552
637
  if (degradations.length > 0) {
@@ -601,7 +686,7 @@ export abstract class AgentRuntimeKernel<
601
686
  async (created) => {
602
687
  await onCreated?.();
603
688
  try {
604
- await this.assembly().bindings.turnEvents?.onApproval?.({
689
+ await this.turnEventsPort()?.onApproval?.({
605
690
  submissionId: submission.submissionId,
606
691
  approvalExecutionId: created.executionId,
607
692
  });
@@ -727,7 +812,7 @@ export abstract class AgentRuntimeKernel<
727
812
  }
728
813
 
729
814
  private async drainRuntimeEvents(idempotentRetry = true): Promise<void> {
730
- const turnEvents = this.runtimeSnapshot?.bindings.turnEvents;
815
+ const turnEvents = this.turnEventsPort();
731
816
  if (!turnEvents) return;
732
817
 
733
818
  let failed = false;
@@ -862,6 +947,41 @@ export abstract class AgentRuntimeKernel<
862
947
  return this.runtimeSnapshot;
863
948
  }
864
949
 
950
+ private async normalizeUIUserInput(
951
+ message: UIMessage & { role: "user" },
952
+ ): Promise<PiCanonicalUserInput> {
953
+ const capabilities = requestedCapabilitiesOf(message.metadata);
954
+ if (capabilities.length === 0) return this.pi.normalizeUserInput(message);
955
+
956
+ await this.ensureRuntimeReady();
957
+ const installedSkills = new Set(
958
+ this.assembly().bindings.skills.sources.map(({ name }) => name),
959
+ );
960
+ const context: string[] = [];
961
+ for (const capability of capabilities) {
962
+ if (capability.kind === "skill") {
963
+ if (!installedSkills.has(capability.name)) {
964
+ throw new Error(
965
+ `Requested Skill is not installed: ${capability.name}`,
966
+ );
967
+ }
968
+ context.push(
969
+ `requested capability: skill/${capability.name}`,
970
+ `required action: call activate_skill for "${capability.name}" before handling the task`,
971
+ );
972
+ continue;
973
+ }
974
+ if (capability.name !== "plan") {
975
+ throw new Error(`Unknown plan capability: ${capability.name}`);
976
+ }
977
+ context.push(
978
+ "requested capability: plan/plan",
979
+ "required action: call update_plan with the complete plan before other work, then wait for user confirmation before execution",
980
+ );
981
+ }
982
+ return this.pi.normalizeUserInput(message, context.join("\n"));
983
+ }
984
+
865
985
  // 作用:把当前已安装的 Runtime Snapshot 投影成可序列化的调试视图。
866
986
  // 调用:Session facet 的 `getRuntimeAssembly` RPC。
867
987
  // 原因:UI 需要读取真实装配结果,而不是上层配置声明。
@@ -2010,7 +2130,9 @@ export abstract class AgentRuntimeKernel<
2010
2130
  continuation: recovery,
2011
2131
  assistantOrdinal,
2012
2132
  onRecord: (record) =>
2013
- this.persistAndBroadcastRecord(turn, record),
2133
+ this.migratedSubmissions.has(submissionId)
2134
+ ? undefined
2135
+ : this.persistAndBroadcastRecord(turn, record),
2014
2136
  onTerminal: (intent) => {
2015
2137
  terminalIntent = intent;
2016
2138
  this.appendTerminalIntent(
@@ -2246,7 +2368,7 @@ export abstract class AgentRuntimeKernel<
2246
2368
  messages,
2247
2369
  }),
2248
2370
  );
2249
- const events = this.assembly().bindings.turnEvents;
2371
+ const events = this.turnEventsPort();
2250
2372
  if (events) {
2251
2373
  try {
2252
2374
  await events.onResponse(
@@ -2395,7 +2517,7 @@ export abstract class AgentRuntimeKernel<
2395
2517
  ReturnType<AgentRuntimeKernel["submitMessage"]>
2396
2518
  >;
2397
2519
  try {
2398
- const normalized = this.pi.normalizeUserInput(latest);
2520
+ const normalized = await this.normalizeUIUserInput(latest);
2399
2521
  submitted = await this.submitMessage(normalized, {
2400
2522
  requestId: event.id,
2401
2523
  idempotencyKey: event.id,
@@ -2480,6 +2602,18 @@ export abstract class AgentRuntimeKernel<
2480
2602
  return this.transcript.browserMessages();
2481
2603
  }
2482
2604
 
2605
+ /** 读取当前 canonical transcript 中最后一条助手文本。 */
2606
+ protected async latestAssistantText(): Promise<string | undefined> {
2607
+ const message = [...await this.transcript.canonicalMessages()]
2608
+ .reverse()
2609
+ .find((entry): entry is AssistantMessage => entry.role === "assistant");
2610
+ const text = message?.content
2611
+ .flatMap((part) => part.type === "text" ? [part.text] : [])
2612
+ .join("")
2613
+ .trim();
2614
+ return text || undefined;
2615
+ }
2616
+
2483
2617
  // #endregion
2484
2618
 
2485
2619
  // #region Runtime Extension Host 回环端口
@@ -2701,9 +2835,18 @@ export abstract class AgentRuntimeKernel<
2701
2835
  };
2702
2836
  }
2703
2837
 
2704
- const userMessage = this.pi.normalizeUserInput(
2705
- message as UIMessage & { role: "user" },
2706
- );
2838
+ let userMessage: PiCanonicalUserInput;
2839
+ try {
2840
+ userMessage = await this.normalizeUIUserInput(
2841
+ message as UIMessage & { role: "user" },
2842
+ );
2843
+ } catch (error) {
2844
+ return {
2845
+ kind: "rejected",
2846
+ code: "invalid_message",
2847
+ message: errorText(error),
2848
+ };
2849
+ }
2707
2850
  // 停在 interaction park 上的 Turn 一律按 steer 处理,哪怕客户端发的是 enqueue:
2708
2851
  // enqueue 要等本 Turn 结束,而本 Turn 正在等一个永远不会来的答案 —— 死锁。
2709
2852
  const parked = this.findInteractionParkedSubmission();
@@ -3194,6 +3337,24 @@ export abstract class AgentRuntimeKernel<
3194
3337
  await this.broadcastApprovals();
3195
3338
  }
3196
3339
 
3340
+ async _cfDetachedNotifyFinish(
3341
+ run: AgentToolRunInfo,
3342
+ result: AgentToolLifecycleResult,
3343
+ ): Promise<void> {
3344
+ const outcome = result.status === "completed"
3345
+ ? result.summary ?? "Completed without a result."
3346
+ : result.error ?? `Background run ${result.status}.`;
3347
+ await this.submitPrompt(
3348
+ [
3349
+ "A background sub-agent run has finished.",
3350
+ `runId: ${run.runId}`,
3351
+ `status: ${result.status}`,
3352
+ `result: ${outcome}`,
3353
+ ].join("\n"),
3354
+ { idempotencyKey: `detached-agent-tool:${run.runId}:${result.status}` },
3355
+ );
3356
+ }
3357
+
3197
3358
  // 作用:把审批、队列和 Session 活动投影到 Agent 可广播状态。
3198
3359
  // 调用:启动、准入、审批变化、interaction 变化、子运行变化和 Turn 完成时调用。
3199
3360
  // 原因:这些都是可重建的 UI 投影,相同内容不重复 `setState`,投影失败也不能阻断执行。
@@ -3306,8 +3467,7 @@ export abstract class AgentRuntimeKernel<
3306
3467
  turn,
3307
3468
  });
3308
3469
  }
3309
- const projection = this.runtimeSnapshot?.bindings.turnEvents
3310
- ?.onActivityChanged?.(nextActivity);
3470
+ const projection = this.turnEventsPort()?.onActivityChanged?.(nextActivity);
3311
3471
  if (projection) {
3312
3472
  this.ctx.waitUntil(
3313
3473
  projection.catch(() => undefined),
@@ -0,0 +1,143 @@
1
+ import type { ExecutionLevel } from "./lib/execution-level";
2
+ import type {
3
+ RuntimeMemoryPort,
4
+ WorkspacePort,
5
+ } from "./kernel/bindings";
6
+ import type { RuntimeExtensionConfig } from "./kernel/extensions";
7
+ import type { RuntimeDegradation } from "./kernel/degradation";
8
+ import type { PiToolCandidate } from "./pi/tool/compiler";
9
+
10
+ /** Tool Surface 筛选策略(不含 Manifest deny 列表)。 */
11
+ export interface ToolSurfaceSelectionPolicy {
12
+ readonly allowsTool?: (name: string) => boolean;
13
+ readonly allowsExtension?: (extension: RuntimeExtensionConfig) => boolean;
14
+ }
15
+
16
+ /** 模型调用 Tool 时传给 `execute` 的上下文。 */
17
+ export interface ToolContext {
18
+ readonly toolCallId: string;
19
+ readonly signal: AbortSignal;
20
+ }
21
+
22
+ /** Definition 作者声明的一个 Tool。 */
23
+ export interface ToolSpec {
24
+ readonly label: string;
25
+ readonly description: string;
26
+ readonly parameters: unknown;
27
+ readonly requiredExecutionLevel: ExecutionLevel;
28
+ /** Require a fresh human decision for every call, regardless of execution level. */
29
+ readonly alwaysRequiresApproval?: boolean;
30
+ readonly execute: (
31
+ input: unknown,
32
+ ctx: ToolContext,
33
+ ) => Promise<unknown>;
34
+ }
35
+
36
+ /** 一次装配声明的全部 Tool,键即模型可见的工具名。 */
37
+ export type ToolRegistry = Record<string, ToolSpec>;
38
+
39
+ export function emptyToolRegistry(): ToolRegistry {
40
+ return {};
41
+ }
42
+
43
+ export function mergeToolRegistries(...registries: ToolRegistry[]): ToolRegistry {
44
+ const merged: ToolRegistry = {};
45
+ for (const registry of registries) {
46
+ for (const [name, spec] of Object.entries(registry)) {
47
+ if (name in merged) {
48
+ throw new Error(`Duplicate Runtime Tool: ${name}`);
49
+ }
50
+ merged[name] = spec;
51
+ }
52
+ }
53
+ return merged;
54
+ }
55
+
56
+ export function toolRegistryFromPiCandidates(
57
+ candidates:
58
+ | readonly PiToolCandidate[]
59
+ | Record<string, PiToolCandidate>,
60
+ ): ToolRegistry {
61
+ const list = Array.isArray(candidates)
62
+ ? candidates
63
+ : Object.values(candidates);
64
+ const registry: ToolRegistry = {};
65
+ for (const candidate of list) {
66
+ const name = candidate.tool.name;
67
+ if (!name.trim()) {
68
+ throw new Error("Pi Tool name must not be empty");
69
+ }
70
+ registry[name] = piCandidateToToolSpec(candidate);
71
+ }
72
+ return registry;
73
+ }
74
+
75
+ export function piCandidateToToolSpec(candidate: PiToolCandidate): ToolSpec {
76
+ const name = candidate.tool.name;
77
+ return {
78
+ label: candidate.tool.label ?? name,
79
+ description: candidate.tool.description,
80
+ parameters: candidate.tool.parameters,
81
+ requiredExecutionLevel: candidate.requiredExecutionLevel,
82
+ ...(candidate.alwaysRequiresApproval
83
+ ? { alwaysRequiresApproval: true }
84
+ : {}),
85
+ execute: async (input, ctx) => {
86
+ const execute = candidate.tool.execute;
87
+ if (typeof execute !== "function") {
88
+ throw new Error(`Tool "${name}" is not executable`);
89
+ }
90
+ const result = await execute(
91
+ ctx.toolCallId,
92
+ input,
93
+ ctx.signal,
94
+ undefined,
95
+ );
96
+ return result.details ?? result;
97
+ },
98
+ };
99
+ }
100
+
101
+ /** Kernel 绑定仍需要的执行后端;不出现在 Definition 公开类型里。 */
102
+ export interface RuntimeToolBindings {
103
+ readonly workspace?: WorkspacePort;
104
+ readonly memory?: RuntimeMemoryPort;
105
+ }
106
+
107
+ /**
108
+ * Worker `tools()` 可返回纯注册表,或附带 Kernel 绑定与降级信息。
109
+ */
110
+ export interface ToolAssemblyResult {
111
+ readonly tools: ToolRegistry;
112
+ readonly bindings?: RuntimeToolBindings;
113
+ readonly memoryProfile?: import("./kernel/profile").RuntimeMemoryProfile;
114
+ readonly enabledSubagents?: readonly string[];
115
+ readonly degradations?: readonly RuntimeDegradation[];
116
+ readonly surfacePolicy?: ToolSurfaceSelectionPolicy;
117
+ readonly commitGuard?: () => Promise<void>;
118
+ /** 释放本次装配创建的外部资源;临时 Agent 终止后由 Runtime 调用。 */
119
+ readonly dispose?: () => Promise<void>;
120
+ }
121
+
122
+ export function normalizeToolAssembly(
123
+ input: ToolRegistry | ToolAssemblyResult,
124
+ ): ToolAssemblyResult {
125
+ if ("tools" in input && typeof input.tools === "object" && input.tools !== null) {
126
+ return input as ToolAssemblyResult;
127
+ }
128
+ return { tools: input as ToolRegistry };
129
+ }
130
+
131
+ /** @deprecated 使用 `ToolSpec`。 */
132
+ export type PlatformToolSpec = ToolSpec;
133
+
134
+ /** @deprecated 使用 `ToolContext`。 */
135
+ export type PlatformToolContext = ToolContext;
136
+
137
+ /** @deprecated 使用 `ToolRegistry`。 */
138
+ export type PlatformToolRegistry = ToolRegistry;
139
+
140
+ /** @deprecated 使用 `emptyToolRegistry`。 */
141
+ export function emptyPlatformToolRegistry(): ToolRegistry {
142
+ return emptyToolRegistry();
143
+ }