@springbrand/agent-runtime 0.2.0-alpha.42 → 0.2.0-alpha.44
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/subagent/tools.ts +2 -5
- package/src/adapter/cloudflare/universal-agent/preparation.ts +1 -8
- package/src/adapter/cloudflare/universal-agent/tools.ts +0 -2
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +15 -0
- package/src/index.ts +1 -0
- package/src/kernel/bindings.ts +14 -0
- package/src/layers/context/budget/gate.ts +3 -3
- package/src/layers/orchestration/temporary-agent/workspace.ts +2 -0
- package/src/lib/prompt.ts +3 -8
- package/src/pi/runtime-adapter/execution.ts +4 -3
- 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 +16 -12
- package/src/pi/tool/compiler.ts +15 -6
- package/src/pi/tool/core-host.ts +141 -56
- package/src/pi/tool/core.ts +3 -2
- package/src/pi/tool/declared.ts +3 -2
- package/src/pi/tool/nested-tools.ts +5 -1
- package/src/pi/tool/schedule.ts +12 -12
- package/src/pi/tool/skill.ts +179 -14
- 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-assembler.ts +13 -6
- package/src/runtime.ts +47 -8
package/package.json
CHANGED
|
@@ -29,9 +29,6 @@ const READONLY_WORKSPACE_TOOLS = new Set([
|
|
|
29
29
|
"grep",
|
|
30
30
|
]);
|
|
31
31
|
const DENIED_TOOLS = [
|
|
32
|
-
// SubAgents have no approval channel; keep the restored high-risk Workspace
|
|
33
|
-
// shell on the main Agent surface, matching the pre-Pi behavior.
|
|
34
|
-
"bash",
|
|
35
32
|
"run_agent",
|
|
36
33
|
"subagents",
|
|
37
34
|
"fanout",
|
|
@@ -46,11 +43,11 @@ const executeParameters = Type.Object({
|
|
|
46
43
|
description:
|
|
47
44
|
"JavaScript to run. Return the final value. The sandbox provides fetch and state.*.",
|
|
48
45
|
}),
|
|
49
|
-
});
|
|
46
|
+
}, { additionalProperties: false });
|
|
50
47
|
|
|
51
48
|
const fetchParameters = Type.Object({
|
|
52
49
|
url: Type.String({ minLength: 1, maxLength: 8_192 }),
|
|
53
|
-
});
|
|
50
|
+
}, { additionalProperties: false });
|
|
54
51
|
|
|
55
52
|
function result(details: unknown): AgentToolResult<unknown> {
|
|
56
53
|
let text: string;
|
|
@@ -55,14 +55,6 @@ const DEFAULT_MEMORY: RuntimeMemoryProfile = Object.freeze({
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
const WORKSPACE_TOOL_NAMES = [
|
|
58
|
-
"read",
|
|
59
|
-
"write",
|
|
60
|
-
"edit",
|
|
61
|
-
"list",
|
|
62
|
-
"find",
|
|
63
|
-
"grep",
|
|
64
|
-
"delete",
|
|
65
|
-
"bash",
|
|
66
58
|
"execute",
|
|
67
59
|
] as const;
|
|
68
60
|
|
|
@@ -157,6 +149,7 @@ export async function prepareWorkspace<Env extends Cloudflare.Env>(
|
|
|
157
149
|
loader: platform.loader,
|
|
158
150
|
outbound: platform.outbound(),
|
|
159
151
|
workspace: workspace.value,
|
|
152
|
+
workspaceAccessMode: "write",
|
|
160
153
|
}),
|
|
161
154
|
degradations: workspace.degradations,
|
|
162
155
|
};
|
|
@@ -12,7 +12,6 @@ import { schedulePiToolCandidates } from "../../../pi/tool/schedule";
|
|
|
12
12
|
import { workspaceRevisionPiToolCandidate } from "../../../pi/tool/workspace-revision";
|
|
13
13
|
import {
|
|
14
14
|
sandboxPiToolCandidates,
|
|
15
|
-
workspacePiToolCandidates,
|
|
16
15
|
} from "../../../pi/tool/workspace-sandbox";
|
|
17
16
|
import type { RuntimeCodeExecutionFactory } from "../../../kernel/bindings";
|
|
18
17
|
import type { ToolAssemblyResult } from "../../../runtime-definition";
|
|
@@ -34,7 +33,6 @@ export function assembleUniversalAgentTools(options: {
|
|
|
34
33
|
}): ToolAssemblyResult {
|
|
35
34
|
const tools = [
|
|
36
35
|
...(options.hostTools ?? []),
|
|
37
|
-
...(options.workspace ? workspacePiToolCandidates(options.workspace) : []),
|
|
38
36
|
...(options.workspaceRevisions
|
|
39
37
|
? [workspaceRevisionPiToolCandidate(options.workspaceRevisions)]
|
|
40
38
|
: []),
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
WorkspaceAdminPort,
|
|
3
3
|
WorkspaceFileInfo,
|
|
4
4
|
WorkspacePort,
|
|
5
|
+
WorkspaceStreamWriteOptions,
|
|
5
6
|
WorkspaceQuota,
|
|
6
7
|
WorkspaceUsage,
|
|
7
8
|
} from "../../../kernel/bindings";
|
|
@@ -216,6 +217,18 @@ export class ScopedWorkspace implements WorkspacePort, WorkspaceAdminPort {
|
|
|
216
217
|
);
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
async writeFileStream(
|
|
221
|
+
path: string,
|
|
222
|
+
content: ReadableStream<Uint8Array>,
|
|
223
|
+
options?: WorkspaceStreamWriteOptions,
|
|
224
|
+
) {
|
|
225
|
+
return this.parent.writeFileStream(
|
|
226
|
+
await this.guardedPhysical(path, true),
|
|
227
|
+
content,
|
|
228
|
+
options,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
219
232
|
async writeFileBytesIfUnchanged(
|
|
220
233
|
path: string,
|
|
221
234
|
data: Uint8Array,
|
|
@@ -363,6 +376,8 @@ export function createWorkspacePortFacade(
|
|
|
363
376
|
workspace.writeFile(path, content, mimeType),
|
|
364
377
|
writeFileBytes: (path, data, mimeType) =>
|
|
365
378
|
workspace.writeFileBytes(path, data, mimeType),
|
|
379
|
+
writeFileStream: (path, content, options) =>
|
|
380
|
+
workspace.writeFileStream(path, content, options),
|
|
366
381
|
appendFile: (path, content, mimeType) =>
|
|
367
382
|
workspace.appendFile(path, content, mimeType),
|
|
368
383
|
exists: (path) => workspace.exists(path),
|
package/src/index.ts
CHANGED
package/src/kernel/bindings.ts
CHANGED
|
@@ -92,6 +92,11 @@ export interface WorkspacePort {
|
|
|
92
92
|
data: Uint8Array | ArrayBuffer,
|
|
93
93
|
mimeType?: string,
|
|
94
94
|
): Promise<void>;
|
|
95
|
+
writeFileStream(
|
|
96
|
+
path: string,
|
|
97
|
+
content: ReadableStream<Uint8Array>,
|
|
98
|
+
options?: WorkspaceStreamWriteOptions,
|
|
99
|
+
): Promise<{ path: string; bytes: number }>;
|
|
95
100
|
/**
|
|
96
101
|
* 把文本追加到文件末尾。
|
|
97
102
|
*
|
|
@@ -205,6 +210,11 @@ export interface WorkspacePort {
|
|
|
205
210
|
glob(pattern: string): Promise<WorkspaceFileInfo[]>;
|
|
206
211
|
}
|
|
207
212
|
|
|
213
|
+
export interface WorkspaceStreamWriteOptions {
|
|
214
|
+
contentLength?: number;
|
|
215
|
+
mediaType?: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
208
218
|
/**
|
|
209
219
|
* 向 Runtime 提供已组装的 Workspace Code Mode 执行能力。
|
|
210
220
|
*
|
|
@@ -845,6 +855,10 @@ export interface RuntimeTurnEventsPort {
|
|
|
845
855
|
readonly submissionId: string;
|
|
846
856
|
readonly approvalExecutionId: string;
|
|
847
857
|
}): Promise<void>;
|
|
858
|
+
/**
|
|
859
|
+
* 至少一次投当前 Session Activity;Host 应按 revision 幂等处理重试。
|
|
860
|
+
* Runtime 恢复时会略过已过时的 revision,避免重放旧 working 状态。
|
|
861
|
+
*/
|
|
848
862
|
onActivityChanged?(
|
|
849
863
|
projection: RuntimeActivityProjection,
|
|
850
864
|
): Promise<void>;
|
|
@@ -173,13 +173,13 @@ export async function spillDurableToolOutput(
|
|
|
173
173
|
// 出口指令必须是可执行的,而且必须只承诺模型真的拿得到的东西:Provider 只把
|
|
174
174
|
// Tool 结果的 content 发给模型,所以这里不能引用只存在于 details 的字段。
|
|
175
175
|
// 溢出产物是 JSON,一个大字符串叶子会整块挤在一行上,而按行分页追不回被行宽
|
|
176
|
-
// 截断的内容 —— 那种情况要走
|
|
176
|
+
// 截断的内容 —— 那种情况要走 state.searchText,不能让模型以为分页读取一定够用。
|
|
177
177
|
note:
|
|
178
178
|
"Output was large and has been saved to the Workspace file above. " +
|
|
179
|
-
"Read it in pages
|
|
179
|
+
"Read it in pages inside execute with state.readFile — do not read it whole; " +
|
|
180
180
|
"each page ends with a footer telling you the line range and the next offset. " +
|
|
181
181
|
"This file is JSON, so a single large value can sit on one very long line: " +
|
|
182
|
-
"if a page reports that lines were cut short, use
|
|
182
|
+
"if a page reports that lines were cut short, use state.searchText on the path instead.",
|
|
183
183
|
};
|
|
184
184
|
} catch {
|
|
185
185
|
return null;
|
|
@@ -83,6 +83,8 @@ export function createTemporaryAgentWorkspace(
|
|
|
83
83
|
// 原因:二进制写入必须与文本写入使用同一 Memory 禁止,不能留下第二条写入路径。
|
|
84
84
|
writeFileBytes: async (path, data, mimeType) =>
|
|
85
85
|
workspace.writeFileBytes(allowed(path), data, mimeType),
|
|
86
|
+
writeFileStream: async (path, content, options) =>
|
|
87
|
+
workspace.writeFileStream(allowed(path), content, options),
|
|
86
88
|
// 作用:在一个允许的文件末尾追加文本。
|
|
87
89
|
// 调用:临时 Agent 需要增量写入时通过 Workspace Tool 调用。
|
|
88
90
|
// 原因:追加也是写操作,必须在委托前拦截 Memory 路径。
|
package/src/lib/prompt.ts
CHANGED
|
@@ -55,7 +55,7 @@ export const TOOLS =
|
|
|
55
55
|
TOOL_ROUTING +
|
|
56
56
|
" Relatedness, repetition, or multiple calls never overrides this availability rule. " +
|
|
57
57
|
"When every required Tool is available inside execute, use one execute for repeated or related calls, branching, or repetition. Inside execute, " +
|
|
58
|
-
"use state.* for workspace file operations, tools.* for other host capabilities,
|
|
58
|
+
"use state.* for workspace file operations, tools.* for other host capabilities, and sequential calls for repeated or durable operations. " +
|
|
59
59
|
"Top-level-only Tools remain Direct even when called repeatedly. " +
|
|
60
60
|
"For web tasks, use web_search for web discovery, current facts, cited research, " +
|
|
61
61
|
"and public URL analysis. For web access specifically, use execute Code Mode only for raw or customized network requests, structured " +
|
|
@@ -65,11 +65,7 @@ export const TOOLS =
|
|
|
65
65
|
"persistent inputs are copied in automatically on first use, /userspace is read-only, and only explicitly " +
|
|
66
66
|
"published /workspace outputs survive. Use Code Mode for computation, multi-step data work, and the " +
|
|
67
67
|
"raw or customized network cases described above; every response and failure it sees is visible to you. " +
|
|
68
|
-
"
|
|
69
|
-
"(no network, no system utilities) and is approval-gated — don't reach for it to read files or fetch. " +
|
|
70
|
-
"When execute is present, make related file changes in one execute with state.*. " +
|
|
71
|
-
"Use read for one existing Workspace file, write to create or replace one file, and edit for one localized change. " +
|
|
72
|
-
"Use bash only when a single shell workflow must coordinate multiple Workspace files; do not use it for a single-file read, write, or edit. " +
|
|
68
|
+
"Every Workspace filesystem operation goes through execute and state.*; no Workspace file methods exist under tools.*. " +
|
|
73
69
|
"Do not re-plan or explain between consecutive tool calls. When a run is within the last five model turns, stop expanding scope and " +
|
|
74
70
|
"prioritize verification, saving durable results, and the final response. " +
|
|
75
71
|
"When related Tool calls can run independently, all are available inside execute, and execute is present, run them inside that execute rather than as parallel top-level calls; " +
|
|
@@ -104,8 +100,7 @@ export const BROWSER =
|
|
|
104
100
|
// Uploaded-file routing prevents lossy markdown conversions from becoming data sources.
|
|
105
101
|
export const FILES =
|
|
106
102
|
"Uploaded files live under /uploads/ in your workspace; convertible formats have a companion " +
|
|
107
|
-
"'<file>.md' (structured markdown). Policy: to summarize
|
|
108
|
-
"read or search multiple files through state.* in one execute instead of repeated top-level file Tool calls. " +
|
|
103
|
+
"'<file>.md' (structured markdown). Policy: to summarize, quote, or search files, use state.* inside execute. " +
|
|
109
104
|
"To compute/aggregate/transform (especially csv/xlsx), write code in execute Code Mode or the Linux Sandbox that reads " +
|
|
110
105
|
"the ORIGINAL file — converted markdown tables are not for computation, and large spreadsheets may " +
|
|
111
106
|
"have no .md at all. For formats without a companion .md (e.g. pptx: unzip and read ppt/slides/*.xml; " +
|
|
@@ -523,6 +523,8 @@ export interface PiTurnDurability {
|
|
|
523
523
|
export interface CreatePreparedPiTurnOptions {
|
|
524
524
|
readonly prepared: PreparedPiRuntime;
|
|
525
525
|
readonly pinnedDescriptor: string;
|
|
526
|
+
/** 同一 Session 的所有 Submission 共用的稳定 Provider 路由身份。 */
|
|
527
|
+
readonly modelSessionId: string;
|
|
526
528
|
readonly submission: {
|
|
527
529
|
readonly id: string;
|
|
528
530
|
readonly requestId: string;
|
|
@@ -653,7 +655,6 @@ export class PreparedPiTurnAdapter {
|
|
|
653
655
|
private readonly turn: PiTurnAdapter;
|
|
654
656
|
private readonly abortController = new AbortController();
|
|
655
657
|
private readonly candidates: readonly PiToolCandidate[];
|
|
656
|
-
private nestedToolOrdinal = 0;
|
|
657
658
|
private assistantOrdinal: number;
|
|
658
659
|
private readonly encoder: PiChunkEncoder;
|
|
659
660
|
private readonly steerMessageIds: string[] = [];
|
|
@@ -829,7 +830,7 @@ export class PreparedPiTurnAdapter {
|
|
|
829
830
|
return execute(toolCallId, input, signal, onUpdate);
|
|
830
831
|
}
|
|
831
832
|
const startedAt = Date.now();
|
|
832
|
-
const telemetryToolCallId =
|
|
833
|
+
const telemetryToolCallId = toolCallId;
|
|
833
834
|
options.onNestedToolStarted?.({
|
|
834
835
|
parentToolCallId: toolCallIdPrefix,
|
|
835
836
|
toolCallId: telemetryToolCallId,
|
|
@@ -906,7 +907,7 @@ export class PreparedPiTurnAdapter {
|
|
|
906
907
|
state.snapshot.bindings.provider,
|
|
907
908
|
state.snapshot.pi.model.id,
|
|
908
909
|
),
|
|
909
|
-
modelSessionId: options.
|
|
910
|
+
modelSessionId: options.modelSessionId,
|
|
910
911
|
...(options.onGeneration ? { onGeneration: options.onGeneration } : {}),
|
|
911
912
|
canonicalMessages: options.canonicalMessages,
|
|
912
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
|
|
|
@@ -197,7 +197,8 @@ export function normalizeUpdatePlanArguments(
|
|
|
197
197
|
steps: steps.map((step) => {
|
|
198
198
|
if (step === null || typeof step !== "object") return step;
|
|
199
199
|
const s = step as Record<string, unknown>;
|
|
200
|
-
const { step:
|
|
200
|
+
const { step: stepAlias, title: titleAlias, ...rest } = s;
|
|
201
|
+
const alias = typeof stepAlias === "string" ? stepAlias : titleAlias;
|
|
201
202
|
const text = typeof rest.text === "string"
|
|
202
203
|
? rest.text
|
|
203
204
|
: typeof alias === "string"
|
|
@@ -227,7 +228,7 @@ const setContextParameters = Type.Object({
|
|
|
227
228
|
], {
|
|
228
229
|
description: 'Whether to replace the block or append to it. Defaults to "replace".',
|
|
229
230
|
})),
|
|
230
|
-
});
|
|
231
|
+
}, { additionalProperties: false });
|
|
231
232
|
|
|
232
233
|
function result<T>(details: T): AgentToolResult<T> {
|
|
233
234
|
return {
|
|
@@ -241,7 +242,10 @@ function candidate<T extends TSchema>(tool: AgentTool<T>): PiToolCandidate {
|
|
|
241
242
|
owner: "runtime-base",
|
|
242
243
|
requiredExecutionLevel: "safe",
|
|
243
244
|
source: "action",
|
|
244
|
-
tool
|
|
245
|
+
tool: {
|
|
246
|
+
...tool,
|
|
247
|
+
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
248
|
+
} as AgentTool<T>,
|
|
245
249
|
};
|
|
246
250
|
}
|
|
247
251
|
|
|
@@ -307,7 +311,7 @@ export function basePiToolCandidates(
|
|
|
307
311
|
return result({ noted: true, count: input.items.length });
|
|
308
312
|
},
|
|
309
313
|
}),
|
|
310
|
-
|
|
314
|
+
exposureMode: "direct",
|
|
311
315
|
},
|
|
312
316
|
{
|
|
313
317
|
...candidate({
|
|
@@ -325,7 +329,7 @@ export function basePiToolCandidates(
|
|
|
325
329
|
});
|
|
326
330
|
},
|
|
327
331
|
}),
|
|
328
|
-
|
|
332
|
+
exposureMode: "direct",
|
|
329
333
|
},
|
|
330
334
|
...(webSearch ? [webSearchPiToolCandidate(webSearch)] : []),
|
|
331
335
|
];
|
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -43,15 +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;
|
|
51
|
-
/**
|
|
52
|
-
readonly
|
|
53
|
-
/** Offer this Tool only through Code Mode, never as a top-level Tool. */
|
|
54
|
-
readonly codeExecutionOnly?: true;
|
|
53
|
+
/** Omitted means visible both directly and through Code Mode. */
|
|
54
|
+
readonly exposureMode?: ToolExposureMode;
|
|
55
55
|
/** Conservative maximum used in the stable Runtime descriptor. */
|
|
56
56
|
readonly requiredExecutionLevel: ExecutionLevel;
|
|
57
57
|
/** Trusted parameter-level policy, evaluated before approval or dispatch. */
|
|
@@ -310,12 +310,21 @@ function governedTool(
|
|
|
310
310
|
candidate: PiToolCandidate,
|
|
311
311
|
options: CompilePiToolsOptions,
|
|
312
312
|
): AgentTool<any, any> {
|
|
313
|
-
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;
|
|
314
323
|
const state = options.governance?.[governanceState];
|
|
315
324
|
const settle = options.settle;
|
|
316
325
|
|
|
317
326
|
return {
|
|
318
|
-
...
|
|
327
|
+
...tool,
|
|
319
328
|
...(candidate.deferLoading ? { deferLoading: true as const } : {}),
|
|
320
329
|
// 执行一次完整的受治理 Pi 工具调用。
|
|
321
330
|
// Pi 工具循环选中编译后的工具时调用,调用方应传入稳定 toolCall id 供结算去重。
|