@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.
- package/package.json +1 -1
- package/src/adapter/cloudflare/resources/r2-skill-source.ts +215 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +1 -18
- package/src/adapter/cloudflare/subagent/tools.ts +2 -5
- package/src/adapter/cloudflare/universal-agent/preparation.ts +21 -9
- package/src/adapter/cloudflare/universal-agent/tools.ts +3 -2
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +15 -0
- package/src/index.ts +3 -0
- package/src/kernel/bindings.ts +38 -6
- package/src/layers/context/budget/gate.ts +3 -3
- package/src/layers/orchestration/temporary-agent/workspace.ts +2 -0
- package/src/lib/prompt.ts +30 -14
- package/src/pi/assembly/snapshot.ts +7 -1
- package/src/pi/runtime-adapter/assembly.ts +8 -3
- package/src/pi/runtime-adapter/execution.ts +108 -13
- package/src/pi/runtime-adapter/models.ts +9 -6
- package/src/pi/runtime-adapter/openrouter-messages.ts +10 -3
- package/src/pi/tool/base.ts +42 -14
- package/src/pi/tool/compiler.ts +15 -2
- package/src/pi/tool/core-host.ts +228 -1
- package/src/pi/tool/core.ts +37 -5
- package/src/pi/tool/declared.ts +3 -0
- package/src/pi/tool/nested-tools.ts +5 -1
- package/src/pi/tool/schedule.ts +12 -10
- package/src/pi/tool/skill.ts +240 -86
- package/src/pi/tool/subagent.ts +2 -0
- package/src/pi/tool/time.ts +1 -1
- package/src/pi/tool/web-fetch.ts +1 -1
- package/src/pi/tool/web-search/web-search.ts +2 -1
- package/src/pi/tool/workspace-revision.ts +2 -1
- package/src/pi/tool/workspace-sandbox.ts +10 -21
- package/src/runtime-agent.ts +3 -0
- package/src/runtime-assembler.ts +105 -7
- package/src/runtime-definition.ts +1 -0
- package/src/runtime.ts +50 -0
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import type { RuntimeProfile } from "../../kernel/profile";
|
|
11
11
|
import type { ExecutionLevel } from "../../lib/execution-level";
|
|
12
12
|
import type { RuntimeSnapshot } from "../../runtime-assembler";
|
|
13
|
-
import type { PiRuntimeAssembly } from "../assembly";
|
|
13
|
+
import type { FinalizedPiToolSurface, PiRuntimeAssembly } from "../assembly";
|
|
14
14
|
import {
|
|
15
15
|
assemblePiSystemContext,
|
|
16
16
|
assemblePiExtensions,
|
|
@@ -77,7 +77,7 @@ interface PreparedPiAssembly {
|
|
|
77
77
|
readonly loaded: readonly PiLoadedExtension[];
|
|
78
78
|
readonly context: readonly PiExtensionContextContribution[];
|
|
79
79
|
readonly degradations: PiExtensionAssembly["degradations"];
|
|
80
|
-
readonly toolSurface:
|
|
80
|
+
readonly toolSurface: FinalizedPiToolSurface;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
@@ -193,6 +193,7 @@ export interface PreparedPiRuntimeState {
|
|
|
193
193
|
readonly snapshot: RuntimeSnapshot;
|
|
194
194
|
readonly assembly: PreparedPiAssembly;
|
|
195
195
|
readonly candidates: readonly PiToolCandidate[];
|
|
196
|
+
readonly codeExecutionCandidates: readonly PiToolCandidate[];
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
// #endregion
|
|
@@ -208,6 +209,7 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
|
|
|
208
209
|
"bind_resource",
|
|
209
210
|
"delete",
|
|
210
211
|
"edit",
|
|
212
|
+
"execute",
|
|
211
213
|
"find",
|
|
212
214
|
"get_time",
|
|
213
215
|
"grep",
|
|
@@ -446,7 +448,7 @@ export async function preparePiRuntime(
|
|
|
446
448
|
owner: object,
|
|
447
449
|
): Promise<PreparedPiRuntime> {
|
|
448
450
|
const assembly = await preparePiAssembly(options);
|
|
449
|
-
const candidates = assembly.toolSurface;
|
|
451
|
+
const candidates = assembly.toolSurface.candidates;
|
|
450
452
|
return Object.freeze(new PreparedRuntime(
|
|
451
453
|
describeRuntime(options.snapshot, candidates, assembly.loaded),
|
|
452
454
|
Object.freeze([
|
|
@@ -460,6 +462,9 @@ export async function preparePiRuntime(
|
|
|
460
462
|
snapshot: options.snapshot,
|
|
461
463
|
assembly,
|
|
462
464
|
candidates: Object.freeze([...candidates]),
|
|
465
|
+
codeExecutionCandidates: Object.freeze([
|
|
466
|
+
...assembly.toolSurface.codeExecutionCandidates,
|
|
467
|
+
]),
|
|
463
468
|
}),
|
|
464
469
|
));
|
|
465
470
|
}
|
|
@@ -25,6 +25,7 @@ import { PiChunkEncoder } from "../message";
|
|
|
25
25
|
import type { UIMessageChunk } from "ai";
|
|
26
26
|
import { ChatStreamStalledError } from "agents/chat";
|
|
27
27
|
import {
|
|
28
|
+
codeExecutionPiToolCandidate,
|
|
28
29
|
compilePiTools,
|
|
29
30
|
createPiToolGovernance,
|
|
30
31
|
normalizeUpdatePlanArguments,
|
|
@@ -522,6 +523,8 @@ export interface PiTurnDurability {
|
|
|
522
523
|
export interface CreatePreparedPiTurnOptions {
|
|
523
524
|
readonly prepared: PreparedPiRuntime;
|
|
524
525
|
readonly pinnedDescriptor: string;
|
|
526
|
+
/** 同一 Session 的所有 Submission 共用的稳定 Provider 路由身份。 */
|
|
527
|
+
readonly modelSessionId: string;
|
|
525
528
|
readonly submission: {
|
|
526
529
|
readonly id: string;
|
|
527
530
|
readonly requestId: string;
|
|
@@ -542,6 +545,25 @@ export interface CreatePreparedPiTurnOptions {
|
|
|
542
545
|
readonly durability: PiTurnDurability;
|
|
543
546
|
/** Reports paired model-generation lifecycle facts to the Runtime owner. */
|
|
544
547
|
readonly onGeneration?: PiGenerationLifecycleObserver;
|
|
548
|
+
/** Reports Code Mode child Tool starts without adding Pi recovery state. */
|
|
549
|
+
readonly onNestedToolStarted?: (input: Readonly<{
|
|
550
|
+
parentToolCallId: string;
|
|
551
|
+
toolCallId: string;
|
|
552
|
+
toolName: string;
|
|
553
|
+
input: unknown;
|
|
554
|
+
occurredAt: number;
|
|
555
|
+
}>) => void;
|
|
556
|
+
/** Reports Code Mode child Tool outcomes without adding Pi settlements. */
|
|
557
|
+
readonly onNestedToolFinished?: (input: Readonly<{
|
|
558
|
+
parentToolCallId: string;
|
|
559
|
+
toolCallId: string;
|
|
560
|
+
toolName: string;
|
|
561
|
+
outcome: "completed" | "failed" | "cancelled";
|
|
562
|
+
durationMs: number;
|
|
563
|
+
output?: import("@earendil-works/pi-agent-core").AgentToolResult<unknown>;
|
|
564
|
+
error?: unknown;
|
|
565
|
+
occurredAt: number;
|
|
566
|
+
}>) => void;
|
|
545
567
|
/** Per-Submission executors for tools whose metadata is fixed at assembly time. */
|
|
546
568
|
readonly toolExecutors?: Readonly<Record<
|
|
547
569
|
string,
|
|
@@ -665,6 +687,8 @@ export class PreparedPiTurnAdapter {
|
|
|
665
687
|
});
|
|
666
688
|
const bindCandidate = (
|
|
667
689
|
candidate: PiToolCandidate,
|
|
690
|
+
toolCallIdPrefix?: string,
|
|
691
|
+
recordRecoveryAttempt = true,
|
|
668
692
|
): PiToolCandidate => ({
|
|
669
693
|
...candidate,
|
|
670
694
|
tool: {
|
|
@@ -673,6 +697,9 @@ export class PreparedPiTurnAdapter {
|
|
|
673
697
|
// 实时运行由 PiCore 调用它,恢复或审批续跑则由 retryTool 进入同一路径。
|
|
674
698
|
// 顺序不能随便调整:门禁先于结果复用,审批可能生成结果,不确定的非幂等工作不能重放。
|
|
675
699
|
execute: async (toolCallId, input, signal, onUpdate) => {
|
|
700
|
+
if (toolCallIdPrefix) {
|
|
701
|
+
toolCallId = `${toolCallIdPrefix}:${toolCallId}`;
|
|
702
|
+
}
|
|
676
703
|
const requiredExecutionLevel = candidate.requiredExecutionLevelForInput
|
|
677
704
|
? await candidate.requiredExecutionLevelForInput(input)
|
|
678
705
|
: candidate.requiredExecutionLevel;
|
|
@@ -783,25 +810,93 @@ export class PreparedPiTurnAdapter {
|
|
|
783
810
|
}
|
|
784
811
|
return responded.result;
|
|
785
812
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
813
|
+
if (recordRecoveryAttempt) {
|
|
814
|
+
const retry = piToolRetryPolicy(candidate);
|
|
815
|
+
const firstAttempt = options.durability.appendToolInput({
|
|
816
|
+
toolCallId,
|
|
817
|
+
toolName: candidate.tool.name,
|
|
818
|
+
input,
|
|
819
|
+
retry,
|
|
820
|
+
});
|
|
821
|
+
if (!firstAttempt && retry === "non-idempotent") {
|
|
822
|
+
throw new Error(
|
|
823
|
+
`Non-idempotent Tool outcome is uncertain after recovery: ${candidate.tool.name}`,
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const execute = options.toolExecutors?.[candidate.tool.name] ??
|
|
828
|
+
candidate.tool.execute;
|
|
829
|
+
if (recordRecoveryAttempt || !toolCallIdPrefix) {
|
|
830
|
+
return execute(toolCallId, input, signal, onUpdate);
|
|
831
|
+
}
|
|
832
|
+
const startedAt = Date.now();
|
|
833
|
+
const telemetryToolCallId = toolCallId;
|
|
834
|
+
options.onNestedToolStarted?.({
|
|
835
|
+
parentToolCallId: toolCallIdPrefix,
|
|
836
|
+
toolCallId: telemetryToolCallId,
|
|
789
837
|
toolName: candidate.tool.name,
|
|
790
838
|
input,
|
|
791
|
-
|
|
839
|
+
occurredAt: startedAt,
|
|
792
840
|
});
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
841
|
+
try {
|
|
842
|
+
const output = await execute(toolCallId, input, signal, onUpdate);
|
|
843
|
+
const occurredAt = Date.now();
|
|
844
|
+
options.onNestedToolFinished?.({
|
|
845
|
+
parentToolCallId: toolCallIdPrefix,
|
|
846
|
+
toolCallId: telemetryToolCallId,
|
|
847
|
+
toolName: candidate.tool.name,
|
|
848
|
+
outcome: "completed",
|
|
849
|
+
durationMs: occurredAt - startedAt,
|
|
850
|
+
output,
|
|
851
|
+
occurredAt,
|
|
852
|
+
});
|
|
853
|
+
return output;
|
|
854
|
+
} catch (error) {
|
|
855
|
+
const occurredAt = Date.now();
|
|
856
|
+
options.onNestedToolFinished?.({
|
|
857
|
+
parentToolCallId: toolCallIdPrefix,
|
|
858
|
+
toolCallId: telemetryToolCallId,
|
|
859
|
+
toolName: candidate.tool.name,
|
|
860
|
+
outcome: signal?.aborted ? "cancelled" : "failed",
|
|
861
|
+
durationMs: occurredAt - startedAt,
|
|
862
|
+
error,
|
|
863
|
+
occurredAt,
|
|
864
|
+
});
|
|
865
|
+
throw error;
|
|
797
866
|
}
|
|
798
|
-
const execute = options.toolExecutors?.[candidate.tool.name] ??
|
|
799
|
-
candidate.tool.execute;
|
|
800
|
-
return execute(toolCallId, input, signal, onUpdate);
|
|
801
867
|
},
|
|
802
868
|
},
|
|
803
869
|
});
|
|
804
|
-
this.candidates = state.candidates.map(
|
|
870
|
+
this.candidates = state.candidates.map((candidate) => {
|
|
871
|
+
if (candidate.tool.name !== "execute") return bindCandidate(candidate);
|
|
872
|
+
const factory = state.snapshot.bindings.codeExecution;
|
|
873
|
+
if (!factory) {
|
|
874
|
+
throw new Error("Prepared Code Mode Tool requires a Runtime factory");
|
|
875
|
+
}
|
|
876
|
+
return bindCandidate({
|
|
877
|
+
...candidate,
|
|
878
|
+
tool: {
|
|
879
|
+
...candidate.tool,
|
|
880
|
+
execute: (toolCallId, input, signal, onUpdate) => {
|
|
881
|
+
const runtimeCandidate = codeExecutionPiToolCandidate(
|
|
882
|
+
factory.create(state.codeExecutionCandidates.map((inner) =>
|
|
883
|
+
// Code Mode owns replay of its connector calls. Pi persists
|
|
884
|
+
// only the parent execute attempt/result; recording an inner
|
|
885
|
+
// input without a Pi settlement creates an orphan recovery
|
|
886
|
+
// action that cannot be found on the direct Tool surface.
|
|
887
|
+
bindCandidate(inner, toolCallId, false)
|
|
888
|
+
)),
|
|
889
|
+
);
|
|
890
|
+
return runtimeCandidate.tool.execute(
|
|
891
|
+
toolCallId,
|
|
892
|
+
input,
|
|
893
|
+
signal,
|
|
894
|
+
onUpdate,
|
|
895
|
+
);
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
});
|
|
899
|
+
});
|
|
805
900
|
this.turn = new PiTurnAdapter({
|
|
806
901
|
pi: {
|
|
807
902
|
model: state.snapshot.pi.model,
|
|
@@ -812,7 +907,7 @@ export class PreparedPiTurnAdapter {
|
|
|
812
907
|
state.snapshot.bindings.provider,
|
|
813
908
|
state.snapshot.pi.model.id,
|
|
814
909
|
),
|
|
815
|
-
modelSessionId: options.
|
|
910
|
+
modelSessionId: options.modelSessionId,
|
|
816
911
|
...(options.onGeneration ? { onGeneration: options.onGeneration } : {}),
|
|
817
912
|
canonicalMessages: options.canonicalMessages,
|
|
818
913
|
// 被中断的 Turn 不写结算。它退场路上还会吐出「工具被中止」,而工具其实
|
|
@@ -614,6 +614,7 @@ function configuredModel(
|
|
|
614
614
|
...endpoint.headers,
|
|
615
615
|
};
|
|
616
616
|
const openRouterProviderPin = endpoint.openRouterProviderPins?.[modelId];
|
|
617
|
+
const supportsStrictTools = modelId.startsWith("anthropic/claude-");
|
|
617
618
|
return {
|
|
618
619
|
...metadata,
|
|
619
620
|
api: apiFor(endpoint.protocol),
|
|
@@ -623,15 +624,17 @@ function configuredModel(
|
|
|
623
624
|
? {
|
|
624
625
|
compat: {
|
|
625
626
|
supportsEagerToolInputStreaming: false,
|
|
627
|
+
supportsStrictTools,
|
|
626
628
|
supportsToolReferences: true,
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
629
|
+
openRouterRouting: {
|
|
630
|
+
require_parameters: true,
|
|
631
|
+
...(openRouterProviderPin
|
|
632
|
+
? {
|
|
630
633
|
order: [openRouterProviderPin],
|
|
631
634
|
allow_fallbacks: false,
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
+
}
|
|
636
|
+
: {}),
|
|
637
|
+
},
|
|
635
638
|
},
|
|
636
639
|
}
|
|
637
640
|
: {}),
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
|
|
11
11
|
type Payload = Record<string, unknown>;
|
|
12
12
|
|
|
13
|
+
const STRUCTURED_OUTPUTS_BETA = "structured-outputs-2025-11-13";
|
|
14
|
+
|
|
13
15
|
function record(value: unknown): Payload | undefined {
|
|
14
16
|
return value !== null && typeof value === "object"
|
|
15
17
|
? value as Payload
|
|
@@ -56,9 +58,11 @@ function decoratePayload(
|
|
|
56
58
|
return {
|
|
57
59
|
...body,
|
|
58
60
|
...(sessionId ? { session_id: sessionId } : {}),
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
provider: {
|
|
62
|
+
...record(body.provider),
|
|
63
|
+
...model.compat?.openRouterRouting,
|
|
64
|
+
require_parameters: true,
|
|
65
|
+
},
|
|
62
66
|
...(deferred.length > 0
|
|
63
67
|
? {
|
|
64
68
|
tools: [
|
|
@@ -90,6 +94,9 @@ function openRouterOptions(
|
|
|
90
94
|
apiKey: undefined,
|
|
91
95
|
headers: {
|
|
92
96
|
...headers,
|
|
97
|
+
...(model.compat?.supportsStrictTools === true
|
|
98
|
+
? { "x-anthropic-beta": STRUCTURED_OUTPUTS_BETA }
|
|
99
|
+
: {}),
|
|
93
100
|
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
|
|
94
101
|
"x-api-key": null,
|
|
95
102
|
},
|
package/src/pi/tool/base.ts
CHANGED
|
@@ -31,7 +31,7 @@ const askUserQuestion = Type.Object({
|
|
|
31
31
|
description:
|
|
32
32
|
"Whether the user may add free text. Defaults to false with options and true without options.",
|
|
33
33
|
})),
|
|
34
|
-
});
|
|
34
|
+
}, { additionalProperties: false });
|
|
35
35
|
|
|
36
36
|
const askUserParameters = Type.Object({
|
|
37
37
|
questions: Type.Array(askUserQuestion, {
|
|
@@ -40,7 +40,7 @@ const askUserParameters = Type.Object({
|
|
|
40
40
|
description:
|
|
41
41
|
"All user decisions needed to continue. Ask them together in one call.",
|
|
42
42
|
}),
|
|
43
|
-
});
|
|
43
|
+
}, { additionalProperties: false });
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* `ask_user` 的客户端响应体。
|
|
@@ -52,8 +52,8 @@ const askUserResponse = Type.Object({
|
|
|
52
52
|
answers: Type.Array(Type.Object({
|
|
53
53
|
selections: Type.Array(Type.String()),
|
|
54
54
|
text: Type.Optional(Type.String()),
|
|
55
|
-
})),
|
|
56
|
-
});
|
|
55
|
+
}, { additionalProperties: false })),
|
|
56
|
+
}, { additionalProperties: false });
|
|
57
57
|
|
|
58
58
|
type AskUserInput = Static<typeof askUserParameters>;
|
|
59
59
|
type AskUserResponse = Static<typeof askUserResponse>;
|
|
@@ -138,7 +138,7 @@ const suggestFollowupsParameters = Type.Object({
|
|
|
138
138
|
maxItems: 4,
|
|
139
139
|
description: "2-4 concrete, distinct follow-up directions.",
|
|
140
140
|
}),
|
|
141
|
-
});
|
|
141
|
+
}, { additionalProperties: false });
|
|
142
142
|
|
|
143
143
|
const updatePlanParameters = Type.Object({
|
|
144
144
|
steps: Type.Array(Type.Object({
|
|
@@ -153,11 +153,11 @@ const updatePlanParameters = Type.Object({
|
|
|
153
153
|
description:
|
|
154
154
|
'Current status. Use "done" for a finished step; never use "completed".',
|
|
155
155
|
}),
|
|
156
|
-
}), {
|
|
156
|
+
}, { additionalProperties: false }), {
|
|
157
157
|
description:
|
|
158
158
|
"The complete, ordered plan. Overwrites any previously reported plan.",
|
|
159
159
|
}),
|
|
160
|
-
});
|
|
160
|
+
}, { additionalProperties: false });
|
|
161
161
|
|
|
162
162
|
type UpdatePlanArguments = Static<typeof updatePlanParameters>;
|
|
163
163
|
|
|
@@ -183,13 +183,32 @@ export function normalizeUpdatePlanArguments(
|
|
|
183
183
|
return input as UpdatePlanArguments;
|
|
184
184
|
}
|
|
185
185
|
const value = input as Record<string, unknown>;
|
|
186
|
-
|
|
186
|
+
let steps = value.steps;
|
|
187
|
+
if (typeof steps === "string") {
|
|
188
|
+
try {
|
|
189
|
+
steps = JSON.parse(steps);
|
|
190
|
+
} catch {
|
|
191
|
+
return input as UpdatePlanArguments;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!Array.isArray(steps)) return input as UpdatePlanArguments;
|
|
187
195
|
return {
|
|
188
196
|
...value,
|
|
189
|
-
steps:
|
|
197
|
+
steps: steps.map((step) => {
|
|
190
198
|
if (step === null || typeof step !== "object") return step;
|
|
191
199
|
const s = step as Record<string, unknown>;
|
|
192
|
-
|
|
200
|
+
const { step: stepAlias, title: titleAlias, ...rest } = s;
|
|
201
|
+
const alias = typeof stepAlias === "string" ? stepAlias : titleAlias;
|
|
202
|
+
const text = typeof rest.text === "string"
|
|
203
|
+
? rest.text
|
|
204
|
+
: typeof alias === "string"
|
|
205
|
+
? alias
|
|
206
|
+
: undefined;
|
|
207
|
+
return {
|
|
208
|
+
...rest,
|
|
209
|
+
...(text === undefined ? {} : { text }),
|
|
210
|
+
status: normalizeStepStatus(s.status),
|
|
211
|
+
};
|
|
193
212
|
}),
|
|
194
213
|
} as UpdatePlanArguments;
|
|
195
214
|
}
|
|
@@ -209,7 +228,7 @@ const setContextParameters = Type.Object({
|
|
|
209
228
|
], {
|
|
210
229
|
description: 'Whether to replace the block or append to it. Defaults to "replace".',
|
|
211
230
|
})),
|
|
212
|
-
});
|
|
231
|
+
}, { additionalProperties: false });
|
|
213
232
|
|
|
214
233
|
function result<T>(details: T): AgentToolResult<T> {
|
|
215
234
|
return {
|
|
@@ -223,7 +242,10 @@ function candidate<T extends TSchema>(tool: AgentTool<T>): PiToolCandidate {
|
|
|
223
242
|
owner: "runtime-base",
|
|
224
243
|
requiredExecutionLevel: "safe",
|
|
225
244
|
source: "action",
|
|
226
|
-
tool
|
|
245
|
+
tool: {
|
|
246
|
+
...tool,
|
|
247
|
+
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
248
|
+
} as AgentTool<T>,
|
|
227
249
|
};
|
|
228
250
|
}
|
|
229
251
|
|
|
@@ -278,7 +300,8 @@ export function basePiToolCandidates(
|
|
|
278
300
|
},
|
|
279
301
|
},
|
|
280
302
|
},
|
|
281
|
-
|
|
303
|
+
{
|
|
304
|
+
...candidate({
|
|
282
305
|
name: "suggest_followups",
|
|
283
306
|
label: "Suggest follow-ups",
|
|
284
307
|
description:
|
|
@@ -288,7 +311,10 @@ export function basePiToolCandidates(
|
|
|
288
311
|
return result({ noted: true, count: input.items.length });
|
|
289
312
|
},
|
|
290
313
|
}),
|
|
291
|
-
|
|
314
|
+
exposureMode: "direct",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
...candidate({
|
|
292
318
|
name: "update_plan",
|
|
293
319
|
label: "Update plan",
|
|
294
320
|
description:
|
|
@@ -303,6 +329,8 @@ export function basePiToolCandidates(
|
|
|
303
329
|
});
|
|
304
330
|
},
|
|
305
331
|
}),
|
|
332
|
+
exposureMode: "direct",
|
|
333
|
+
},
|
|
306
334
|
...(webSearch ? [webSearchPiToolCandidate(webSearch)] : []),
|
|
307
335
|
];
|
|
308
336
|
}
|
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -43,11 +43,15 @@ export interface PiToolInteractionSpec {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
|
|
46
|
+
export type ToolExposureMode = "direct" | "codemode" | "both";
|
|
47
|
+
|
|
46
48
|
export interface PiToolCandidate {
|
|
47
49
|
readonly owner: string;
|
|
48
50
|
readonly tool: AgentTool<any, any>;
|
|
49
51
|
/** @internal Send the complete schema but hide it until provider Tool Search finds it. */
|
|
50
52
|
readonly deferLoading?: true;
|
|
53
|
+
/** Omitted means visible both directly and through Code Mode. */
|
|
54
|
+
readonly exposureMode?: ToolExposureMode;
|
|
51
55
|
/** Conservative maximum used in the stable Runtime descriptor. */
|
|
52
56
|
readonly requiredExecutionLevel: ExecutionLevel;
|
|
53
57
|
/** Trusted parameter-level policy, evaluated before approval or dispatch. */
|
|
@@ -306,12 +310,21 @@ function governedTool(
|
|
|
306
310
|
candidate: PiToolCandidate,
|
|
307
311
|
options: CompilePiToolsOptions,
|
|
308
312
|
): AgentTool<any, any> {
|
|
309
|
-
const
|
|
313
|
+
const tool = candidate.tool.constrainedSampling === false
|
|
314
|
+
? candidate.tool
|
|
315
|
+
: {
|
|
316
|
+
...candidate.tool,
|
|
317
|
+
constrainedSampling: candidate.tool.constrainedSampling ?? {
|
|
318
|
+
type: "json_schema" as const,
|
|
319
|
+
strict: "prefer" as const,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
const execute = tool.execute;
|
|
310
323
|
const state = options.governance?.[governanceState];
|
|
311
324
|
const settle = options.settle;
|
|
312
325
|
|
|
313
326
|
return {
|
|
314
|
-
...
|
|
327
|
+
...tool,
|
|
315
328
|
...(candidate.deferLoading ? { deferLoading: true as const } : {}),
|
|
316
329
|
// 执行一次完整的受治理 Pi 工具调用。
|
|
317
330
|
// Pi 工具循环选中编译后的工具时调用,调用方应传入稳定 toolCall id 供结算去重。
|