@springbrand/agent-runtime 0.2.0-alpha.34 → 0.2.0-alpha.38

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.
@@ -35,13 +35,8 @@ import {
35
35
  } from "./runtime-assembler";
36
36
  import type {
37
37
  ResolvedResources,
38
- RuntimeAgentHooks,
38
+ ToolAssemblyResult,
39
39
  } from "./runtime-definition";
40
- import {
41
- normalizeToolAssembly,
42
- type ToolAssemblyResult,
43
- type ToolRegistry,
44
- } from "./tool-registry";
45
40
  import type { RuntimeAssemblyView } from "./kernel/runtime-assembly-view";
46
41
  import type { RuntimeConfigUpdateResult } from "./kernel/runtime-config";
47
42
  import {
@@ -82,6 +77,7 @@ export interface LoadedRuntime<Config> {
82
77
  readonly runtimeKey: string;
83
78
  readonly config: Config;
84
79
  readonly resources: ResolvedResources;
80
+ readonly commitGuard?: () => Promise<void>;
85
81
  }
86
82
 
87
83
  export interface RuntimeConfigUpdate<Change> {
@@ -108,12 +104,7 @@ interface RuntimeAgentDefinitionBase<
108
104
  readonly tools: (
109
105
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
110
106
  config: Config,
111
- ) => ToolRegistry | ToolAssemblyResult | Promise<ToolRegistry | ToolAssemblyResult>;
112
-
113
- readonly hooks?:
114
- | RuntimeAgentHooks<Env, Config, Command, Change>
115
- | ((context: RuntimeAgentPlanningContext<Env, Command, Change>) =>
116
- RuntimeAgentHooks<Env, Config, Command, Change>);
107
+ ) => ToolAssemblyResult | Promise<ToolAssemblyResult>;
117
108
 
118
109
  /** Prepare the optional telemetry consumer for this concrete Agent facet. */
119
110
  readonly telemetry?: (
@@ -411,18 +402,6 @@ export function defineRuntimeAgent<
411
402
  | MutableRuntimeAgentDefinition<Env, Config, Command, Change>
412
403
  | ReadonlyRuntimeAgentDefinition<Env, Config>,
413
404
  ): RuntimeAgentClass<Env, Command, Change> {
414
- const resolveDefinitionHooks = (
415
- context: RuntimeAgentPlanningContext<Env, Command, Change>,
416
- ): RuntimeAgentHooks<Env, Config, Command, Change> | undefined => {
417
- const hooks = definition.hooks;
418
- if (!hooks) return undefined;
419
- return typeof hooks === "function"
420
- ? (hooks as (
421
- ctx: RuntimeAgentPlanningContext<Env, Command, Change>,
422
- ) => RuntimeAgentHooks<Env, Config, Command, Change>)(context)
423
- : hooks as RuntimeAgentHooks<Env, Config, Command, Change>;
424
- };
425
-
426
405
  const resolveDefinitionTelemetry = (
427
406
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
428
407
  ): AgentTelemetryBinding | undefined => {
@@ -628,17 +607,18 @@ export function defineRuntimeAgent<
628
607
  const context = this.createDefinitionContext({
629
608
  value: loaded.config,
630
609
  });
631
- let tools = normalizeToolAssembly(await withRuntimeLoadTimeout(
610
+ let tools = await withRuntimeLoadTimeout(
632
611
  "definition.tools",
633
612
  () => (
634
613
  definition.tools as (
635
614
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
636
615
  config: Config,
637
- ) => ToolRegistry | ToolAssemblyResult | Promise<ToolRegistry | ToolAssemblyResult>
616
+ ) => ToolAssemblyResult | Promise<ToolAssemblyResult>
638
617
  )(context, loaded.config),
639
618
  { timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
640
- ));
641
- let hooks = resolveDefinitionHooks(context);
619
+ );
620
+ let resources = loaded.resources;
621
+ let commitGuard = loaded.commitGuard;
642
622
  if (!this.telemetryResolved) {
643
623
  this.resolvedTelemetry = resolveDefinitionTelemetry(context);
644
624
  this.telemetryResolved = true;
@@ -647,14 +627,14 @@ export function defineRuntimeAgent<
647
627
  if (this.role === "temporary") {
648
628
  this.temporaryDispose = tools.dispose;
649
629
  tools = this.applyTemporaryToolPolicy(tools);
650
- hooks = await this.createTemporaryHooks();
630
+ resources = await this.applyTemporaryResources(resources);
631
+ commitGuard = undefined;
651
632
  }
652
633
  const candidate = await assembleRuntimeSnapshot({
653
634
  ctx: context,
654
- config: loaded.config,
655
635
  tools,
656
- resources: loaded.resources,
657
- hooks,
636
+ resources,
637
+ ...(commitGuard ? { commitGuard } : {}),
658
638
  ...(telemetry ? { telemetry } : {}),
659
639
  });
660
640
 
@@ -713,9 +693,9 @@ export function defineRuntimeAgent<
713
693
  };
714
694
  }
715
695
 
716
- private async createTemporaryHooks(): Promise<
717
- RuntimeAgentHooks<Env, Config, Command, Change>
718
- > {
696
+ private async applyTemporaryResources(
697
+ resources: ResolvedResources,
698
+ ): Promise<ResolvedResources> {
719
699
  const launch = this.temporaryLaunch ??
720
700
  await this.ctx.storage.get<TemporaryAgentLaunch<Config>>(
721
701
  TEMPORARY_AGENT_LAUNCH_KEY,
@@ -723,17 +703,27 @@ export function defineRuntimeAgent<
723
703
  if (!launch) throw new Error("Temporary Agent is not initialized");
724
704
  this.temporaryLaunch = launch;
725
705
  return {
726
- profileOverrides: () => ({
727
- systemPrompt: launch.instructions,
728
- executionLevel: "high",
729
- }),
730
- gateTool: async (request) => {
731
- if (!requiresExecutionApproval(
732
- launch.executionLevel,
733
- request.requiredExecutionLevel,
734
- )) return;
735
- await this.requestParentTemporaryApproval(launch, request);
706
+ ...resources,
707
+ settings: {
708
+ ...resources.settings,
709
+ profile: {
710
+ ...resources.settings.profile,
711
+ systemPrompt: launch.instructions,
712
+ executionLevel: "high",
713
+ },
714
+ },
715
+ platform: {
716
+ ...resources.platform,
717
+ gateTool: async (request) => {
718
+ if (!requiresExecutionApproval(
719
+ launch.executionLevel,
720
+ request.requiredExecutionLevel,
721
+ )) return;
722
+ await this.requestParentTemporaryApproval(launch, request);
723
+ },
736
724
  },
725
+ turnEvents: undefined,
726
+ admission: undefined,
737
727
  };
738
728
  }
739
729
 
@@ -7,6 +7,7 @@ import type {
7
7
  RuntimeSandboxPort,
8
8
  RuntimeSchedulePort,
9
9
  RuntimeSkillSourceBinding,
10
+ RuntimeSubmissionAdmissionHook,
10
11
  RuntimeSubagentPort,
11
12
  RuntimeTurnEventsPort,
12
13
  RuntimeBindings,
@@ -29,6 +30,7 @@ import {
29
30
  } from "./lib/execution-level";
30
31
  import {
31
32
  createPiRuntimeAssembly,
33
+ type FinalizedPiToolSurface,
32
34
  type PiToolSurface,
33
35
  type PiRuntimeAssembly,
34
36
  } from "./pi/assembly/snapshot";
@@ -36,19 +38,13 @@ import type { AgentTool } from "@earendil-works/pi-agent-core";
36
38
  import type { PiToolCandidate } from "./pi/tool/compiler";
37
39
  import type {
38
40
  AssembleRuntimeSnapshotInput,
39
- RuntimeAgentHooks,
40
41
  RuntimeSettings,
41
42
  RuntimeExtensionContribution,
42
43
  RuntimeProfileContribution,
43
44
  RuntimeSkillContribution,
44
45
  RuntimeToolSurfacePolicy,
45
46
  } from "./runtime-definition";
46
- import {
47
- toolRegistryFromPiCandidates,
48
- type RuntimeCodeExecutionFactory,
49
- type ToolRegistry,
50
- } from "./tool-registry";
51
- import { withRuntimeLoadTimeout } from "./kernel/runtime-load";
47
+ import type { RuntimeCodeExecutionFactory } from "./kernel/bindings";
52
48
  import {
53
49
  basePiToolCandidates,
54
50
  memoryPiToolCandidate,
@@ -125,8 +121,7 @@ export interface RuntimeSnapshot {
125
121
  export interface RuntimeCandidate {
126
122
  readonly snapshot: RuntimeSnapshot;
127
123
  readonly commitGuards: readonly (() => Promise<void>)[];
128
- readonly hooks?: RuntimeAgentHooks<Cloudflare.Env, unknown, never, never>;
129
- readonly turnEvents?: RuntimeTurnEventsPort;
124
+ readonly admission?: RuntimeSubmissionAdmissionHook;
130
125
  readonly telemetry?: AgentTelemetryBinding;
131
126
  }
132
127
 
@@ -198,9 +193,13 @@ async function createToolSurface(
198
193
  const classify = (candidate: PiToolCandidate): PiToolCandidate =>
199
194
  Object.freeze({
200
195
  ...candidate,
201
- ...(defersTool?.(candidate.tool.name) === true
202
- ? { deferLoading: true as const }
203
- : { deferLoading: undefined }),
196
+ ...(defersTool
197
+ ? {
198
+ deferLoading: defersTool(candidate.tool.name) === true
199
+ ? true as const
200
+ : undefined,
201
+ }
202
+ : {}),
204
203
  });
205
204
  // 平台没有浏览器能力时注册空集:宁可没有这个 Tool,也不注册一个必然失败的 Tool 误导模型。
206
205
  // `create()` 推迟到确认这个名字真的可见之后,被 deny 的装配不白建一个浏览器连接器。
@@ -267,7 +266,10 @@ async function createToolSurface(
267
266
  deny.has("execute") ||
268
267
  allowsTool?.("execute") === false
269
268
  ) {
270
- return Object.freeze(directVisible.map(classify));
269
+ return Object.freeze({
270
+ candidates: Object.freeze(directVisible.map(classify)),
271
+ codeExecutionCandidates: Object.freeze([]),
272
+ }) satisfies FinalizedPiToolSurface;
271
273
  }
272
274
  const mergeable = finalized.filter(
273
275
  (candidate) =>
@@ -275,11 +277,40 @@ async function createToolSurface(
275
277
  !candidate.interaction &&
276
278
  typeof candidate.tool.execute === "function",
277
279
  );
278
- const codeExecutionTools = Object.freeze([...mergeable]);
280
+ const codeExecutionCandidates = Object.freeze([...mergeable]);
281
+ const codeExecutionToolNames = new Set(
282
+ codeExecutionCandidates.map(({ tool }) => tool.name),
283
+ );
279
284
  const codeExecution = input.codeExecution.create(
280
- toolRegistryFromPiCandidates(codeExecutionTools),
285
+ codeExecutionCandidates,
281
286
  );
282
- const materializeGuidance = codeExecutionTools.some(
287
+ const topLevelOnly = directVisible.filter(
288
+ ({ tool }) => !codeExecutionToolNames.has(tool.name),
289
+ );
290
+ const routeList = (deferred: boolean) => {
291
+ const matches = topLevelOnly.filter((candidate) =>
292
+ (defersTool?.(candidate.tool.name) === true) === deferred
293
+ );
294
+ return matches.length > 0
295
+ ? matches.map(({ tool }) =>
296
+ `- \`${tool.name}\` — ${(tool.label ?? tool.name)
297
+ .replaceAll(/\s+/g, " ").trim().replaceAll("`", "'")}`
298
+ ).join("\n")
299
+ : "- None.";
300
+ };
301
+ const routeGuidance = [
302
+ "## Tool routing",
303
+ "Top-level deferred Tools are not callable through `tools.*`. Use Provider Tool Search with their exact name, then call them directly.",
304
+ "Top-level immediate Tools are already available outside execute; call them directly.",
305
+ "",
306
+ "## Top-level deferred Tools",
307
+ routeList(true),
308
+ "",
309
+ "## Top-level immediate Tools",
310
+ routeList(false),
311
+ "",
312
+ ].join("\n");
313
+ const materializeGuidance = codeExecutionCandidates.some(
283
314
  ({ tool }) => tool.name === "materialize_skill_resource",
284
315
  )
285
316
  ? "Skill resource copying is available only inside execute through " +
@@ -287,18 +318,17 @@ async function createToolSurface(
287
318
  "no Direct Tool is registered for it. " +
288
319
  "Use a loop or Promise.all when copying multiple resources.\n\n"
289
320
  : "";
290
- return Object.freeze([
291
- classify({
292
- ...codeExecutionPiToolCandidate(
293
- {
294
- ...codeExecution,
295
- description: materializeGuidance + codeExecution.description,
296
- },
297
- ),
298
- codeExecutionTools,
299
- }),
300
- ...directVisible.map(classify),
301
- ]);
321
+ return Object.freeze({
322
+ candidates: Object.freeze([
323
+ classify(codeExecutionPiToolCandidate({
324
+ ...codeExecution,
325
+ description:
326
+ routeGuidance + "\n" + materializeGuidance + codeExecution.description,
327
+ })),
328
+ ...directVisible.map(classify),
329
+ ]),
330
+ codeExecutionCandidates,
331
+ }) satisfies FinalizedPiToolSurface;
302
332
  },
303
333
  }),
304
334
  extensions: input.extensions.filter(
@@ -658,174 +688,34 @@ export async function assembleRuntime(
658
688
  commit(candidate.snapshot);
659
689
  }
660
690
 
661
- export function toolRegistryPiToolCandidates(
662
- registry: ToolRegistry,
663
- settings?: Pick<RuntimeSettings, "toolPolicy">,
664
- ): PiToolCandidate[] {
665
- const deny = new Set(settings?.toolPolicy?.denyPolicy?.deny ?? []);
666
- const allowsTool = settings?.toolPolicy?.allowsTool;
667
- const candidates: PiToolCandidate[] = [];
668
- for (const [name, spec] of Object.entries(registry)) {
669
- if (deny.has(name)) continue;
670
- if (allowsTool?.(name) === false) continue;
671
- const normalized = requiredName(name, "Platform Tool");
672
- const {
673
- label,
674
- description,
675
- parameters,
676
- execute,
677
- owner = "platform",
678
- ...metadata
679
- } = spec;
680
- candidates.push(Object.freeze({
681
- ...metadata,
682
- owner,
683
- tool: Object.freeze({
684
- name: normalized,
685
- label,
686
- description,
687
- parameters,
688
- async execute(
689
- toolCallId: string,
690
- input: unknown,
691
- signal?: AbortSignal,
692
- ) {
693
- const result = await execute(input, {
694
- toolCallId,
695
- signal: signal ?? new AbortController().signal,
696
- });
697
- if (
698
- result != null &&
699
- typeof result === "object" &&
700
- "content" in result &&
701
- Array.isArray((result as { content?: unknown }).content)
702
- ) {
703
- return result as import("@earendil-works/pi-agent-core").AgentToolResult<unknown>;
704
- }
705
- let text: string;
706
- try {
707
- text = JSON.stringify(result) ?? String(result);
708
- } catch {
709
- text = String(result);
710
- }
711
- return {
712
- content: [{ type: "text" as const, text }],
713
- details: result,
714
- };
715
- },
716
- } as AgentTool<any, any>),
717
- }));
718
- }
719
- return candidates;
720
- }
721
-
722
- function hooksToTurnEventsPort<
723
- Env extends Cloudflare.Env,
724
- Config,
725
- Command,
726
- Change,
727
- >(
728
- ctx: AssembleRuntimeSnapshotInput<Env, Config, Command, Change>["ctx"],
729
- hooks?: RuntimeAgentHooks<Env, Config, Command, Change>,
730
- ): RuntimeTurnEventsPort | undefined {
731
- if (!hooks) return undefined;
732
- if (
733
- !hooks.onTurnEnd &&
734
- !hooks.onModelUsage &&
735
- !hooks.onSubagentUsage &&
736
- !hooks.onLifecycleFact &&
737
- !hooks.onToolStart &&
738
- !hooks.onToolSettled &&
739
- !hooks.onApproval &&
740
- !hooks.onActivityChanged &&
741
- !hooks.onSubmissionTerminal
742
- ) {
743
- return undefined;
744
- }
745
- return {
746
- onResponse: hooks.onTurnEnd
747
- ? (messages) => hooks.onTurnEnd!(ctx, messages)
748
- : async () => undefined,
749
- ...(hooks.onModelUsage ? { onModelUsage: hooks.onModelUsage } : {}),
750
- ...(hooks.onSubagentUsage ? { onSubagentUsage: hooks.onSubagentUsage } : {}),
751
- ...(hooks.onLifecycleFact ? { onLifecycleFact: hooks.onLifecycleFact } : {}),
752
- ...(hooks.onToolStart ? { onToolStart: hooks.onToolStart } : {}),
753
- ...(hooks.onToolSettled ? { onToolSettled: hooks.onToolSettled } : {}),
754
- ...(hooks.onApproval ? { onApproval: hooks.onApproval } : {}),
755
- ...(hooks.onActivityChanged
756
- ? { onActivityChanged: hooks.onActivityChanged }
757
- : {}),
758
- ...(hooks.onSubmissionTerminal
759
- ? { onSubmissionTerminal: hooks.onSubmissionTerminal }
760
- : {}),
761
- };
762
- }
763
-
764
691
  /**
765
- * 从 Config、Tool 和已加载 Resource 装配 Runtime 候选 Snapshot。
692
+ * 从 Tool 和已加载 Resource 装配 Runtime 候选 Snapshot。
766
693
  *
767
694
  * @internal
768
695
  */
769
696
  export async function assembleRuntimeSnapshot<
770
697
  Env extends Cloudflare.Env,
771
- Config,
772
698
  Command = never,
773
699
  Change = never,
774
700
  >(
775
- input: AssembleRuntimeSnapshotInput<Env, Config, Command, Change>,
701
+ input: AssembleRuntimeSnapshotInput<Env, Command, Change>,
776
702
  ): Promise<RuntimeCandidate> {
777
703
  const resources = input.resources;
778
704
  const toolAssembly = input.tools;
779
705
  const settings = resources.settings;
780
706
  const provider = resources.provider;
781
- let profile = settings.profile;
782
- if (input.hooks?.profileOverrides) {
783
- const overrides = await withRuntimeLoadTimeout(
784
- "profile-overrides",
785
- () => input.hooks!.profileOverrides!(
786
- input.ctx,
787
- input.config,
788
- ),
789
- );
790
- if (overrides.systemPrompt !== undefined) {
791
- profile = { ...profile, systemPrompt: overrides.systemPrompt };
792
- }
793
- if (overrides.executionLevel !== undefined) {
794
- profile = { ...profile, executionLevel: overrides.executionLevel };
795
- }
796
- }
707
+ const profile = settings.profile;
797
708
 
798
- const hostTools = toolRegistryPiToolCandidates(
799
- toolAssembly.tools,
800
- settings,
801
- );
709
+ const hostTools = toolAssembly.tools;
802
710
 
803
711
  const commitGuards: Array<() => Promise<void>> = [];
804
712
  if (toolAssembly.commitGuard) commitGuards.push(toolAssembly.commitGuard);
805
- if (input.hooks?.beforeCommit) {
806
- for (const guard of input.hooks.beforeCommit) {
807
- commitGuards.push(() => guard(input.ctx, input.config));
808
- }
809
- }
810
-
811
- const platform: RuntimePlatformPort = input.hooks?.gateTool
812
- ? Object.freeze({
813
- ...resources.platform,
814
- gateTool: (request: {
815
- toolCallId: string;
816
- toolName: string;
817
- input: unknown;
818
- requiredExecutionLevel: import("./lib/execution-level").ExecutionLevel;
819
- signal: AbortSignal;
820
- }) => input.hooks!.gateTool!(request),
821
- })
822
- : resources.platform;
713
+ if (input.commitGuard) commitGuards.push(input.commitGuard);
823
714
 
824
715
  const memoryProfile =
825
716
  toolAssembly.memoryProfile ?? settings.memoryProfile;
826
717
  const enabledSubagents =
827
718
  toolAssembly.enabledSubagents ?? settings.enabledSubagents;
828
- const turnEvents = hooksToTurnEventsPort(input.ctx, input.hooks);
829
719
 
830
720
  const toolPolicy: RuntimeToolSurfacePolicy | undefined =
831
721
  settings.toolPolicy || toolAssembly.surfacePolicy
@@ -860,7 +750,7 @@ export async function assembleRuntimeSnapshot<
860
750
  ...(toolPolicy ? { toolPolicy } : {}),
861
751
  enabledSubagents: [...enabledSubagents],
862
752
  provider,
863
- platform,
753
+ platform: resources.platform,
864
754
  ...(toolAssembly.bindings?.workspace
865
755
  ? { workspace: toolAssembly.bindings.workspace }
866
756
  : {}),
@@ -883,9 +773,9 @@ export async function assembleRuntimeSnapshot<
883
773
  ? { gateway: resources.connectors.gateway }
884
774
  : {}),
885
775
  extensions: resources.extensions,
886
- ...(turnEvents ? { turnEvents } : {}),
776
+ ...(resources.turnEvents ? { turnEvents: resources.turnEvents } : {}),
887
777
  ...(input.telemetry ? { telemetry: input.telemetry } : {}),
888
- commitGuards: [],
778
+ commitGuards,
889
779
  degradations: [
890
780
  ...(toolAssembly.degradations ?? []),
891
781
  ...(resources.degradations ?? []),
@@ -895,22 +785,8 @@ export async function assembleRuntimeSnapshot<
895
785
 
896
786
  const candidate = await prepareRuntimeCandidate(assemblyInput);
897
787
  return Object.freeze({
898
- snapshot: candidate.snapshot,
899
- commitGuards: Object.freeze([
900
- ...candidate.commitGuards,
901
- ...commitGuards,
902
- ]),
903
- ...(input.hooks
904
- ? {
905
- hooks: input.hooks as unknown as RuntimeAgentHooks<
906
- Cloudflare.Env,
907
- unknown,
908
- never,
909
- never
910
- >,
911
- }
912
- : {}),
913
- ...(turnEvents ? { turnEvents } : {}),
788
+ ...candidate,
789
+ ...(resources.admission ? { admission: resources.admission } : {}),
914
790
  ...(candidate.telemetry ? { telemetry: candidate.telemetry } : {}),
915
791
  });
916
792
  }