@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.
- package/package.json +11 -3
- package/src/adapter/cloudflare/index.ts +55 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +1509 -0
- package/src/adapter/cloudflare/sandbox/id.ts +23 -0
- package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
- package/src/adapter/cloudflare/subagent/definition.ts +574 -0
- package/src/adapter/cloudflare/subagent/runner.ts +175 -0
- package/src/adapter/cloudflare/subagent/tools.ts +254 -0
- package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
- package/src/adapter/cloudflare/universal-agent/preparation.ts +273 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +74 -0
- package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
- package/src/agent-tool-runtime.ts +152 -0
- package/src/index.ts +49 -7
- package/src/kernel/bindings.ts +6 -6
- package/src/kernel/recoverable-chat-agent.ts +12 -0
- package/src/kernel/runtime-load.ts +89 -0
- package/src/layers/orchestration/temporary-agent/core.ts +12 -1
- package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
- package/src/lib/mcp.ts +7 -3
- package/src/pi/assembly/context.ts +3 -3
- package/src/pi/assembly/extensions.ts +11 -22
- package/src/pi/assembly/snapshot.ts +1 -1
- package/src/pi/message/contract.ts +7 -0
- package/src/pi/message/conversion.ts +9 -1
- package/src/pi/runtime-adapter/assembly.ts +4 -10
- package/src/pi/runtime-adapter/index.ts +6 -2
- package/src/pi/tool/base.ts +17 -2
- package/src/pi/tool/compiler.ts +0 -1
- package/src/pi/tool/core.ts +13 -3
- package/src/pi/tool/mcp.ts +3 -4
- package/src/pi/tool/schedule.ts +11 -1
- package/src/pi/tool/skill.ts +55 -41
- package/src/pi/tool/subagent.ts +14 -2
- package/src/pi/tool/web-fetch.ts +0 -1
- package/src/pi/tool/web-search/web-search.ts +0 -1
- package/src/pi/tool/workspace-sandbox.ts +15 -7
- package/src/runtime-agent-context.ts +112 -0
- package/src/runtime-agent.ts +442 -328
- package/src/runtime-assembler.ts +255 -103
- package/src/runtime-definition.ts +173 -0
- package/src/runtime.ts +185 -25
- 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 {
|
|
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
|
-
|
|
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
|
|
480
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
622
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.aborted");
|
|
544
623
|
throw error;
|
|
545
624
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2705
|
-
|
|
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.
|
|
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
|
+
}
|