@springbrand/agent-runtime 0.2.0-alpha.39 → 0.2.0-alpha.41
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/universal-agent/preparation.ts +1 -20
- package/src/adapter/cloudflare/universal-agent/tools.ts +0 -3
- package/src/index.ts +0 -2
- package/src/kernel/bindings.ts +6 -28
- package/src/lib/prompt.ts +13 -31
- package/src/pi/assembly/context.ts +4 -1
- package/src/pi/assembly/snapshot.ts +1 -7
- package/src/pi/message/projection.ts +4 -2
- package/src/pi/runtime-adapter/assembly.ts +5 -10
- package/src/pi/runtime-adapter/execution.ts +12 -106
- package/src/pi/tool/base.ts +10 -11
- package/src/pi/tool/compiler.ts +2 -4
- package/src/pi/tool/core-host.ts +1 -143
- package/src/pi/tool/core.ts +3 -34
- package/src/pi/tool/declared.ts +2 -2
- package/src/pi/tool/schedule.ts +1 -3
- package/src/pi/tool/skill.ts +103 -29
- package/src/pi/tool/workspace-sandbox.ts +39 -9
- package/src/runtime-agent.ts +0 -3
- package/src/runtime-assembler.ts +6 -97
- package/src/runtime-definition.ts +0 -1
- package/src/runtime.ts +0 -22
package/src/pi/tool/core-host.ts
CHANGED
|
@@ -1,61 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createWorkspaceStateBackend,
|
|
3
|
-
type WorkspaceFsLike,
|
|
4
|
-
} from "@cloudflare/shell";
|
|
5
1
|
import { createBrowserTools } from "@cloudflare/think/tools/browser";
|
|
6
|
-
import { createExecuteRuntime } from "@cloudflare/think/tools/execute";
|
|
7
2
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
8
|
-
import type { Usage } from "@earendil-works/pi-ai";
|
|
9
3
|
import type { ToolSet } from "ai";
|
|
10
4
|
import type {
|
|
11
5
|
RuntimeBrowserPort,
|
|
12
6
|
RuntimeCodeExecutionPort,
|
|
13
|
-
WorkspacePort,
|
|
14
7
|
} from "../../kernel/bindings";
|
|
15
|
-
import type { RuntimeCodeExecutionFactory } from "../../kernel/bindings";
|
|
16
8
|
import { serializeOutput } from "../../lib/artifacts";
|
|
17
|
-
import type { PiToolCandidate } from "./compiler";
|
|
18
|
-
import { piCandidatesToAiTools } from "./nested-tools";
|
|
19
9
|
|
|
20
|
-
// 本文件沿用 `../../index.ts` 入口定义的
|
|
10
|
+
// 本文件沿用 `../../index.ts` 入口定义的 Browser 和 Port 术语。
|
|
21
11
|
|
|
22
12
|
const CODEMODE_SANDBOX_TIMEOUT_MS = 55_000;
|
|
23
13
|
|
|
24
|
-
function directoryEntry(name: string, label: string): string {
|
|
25
|
-
const singleLineLabel = label.replaceAll(/\s+/g, " ").trim().replaceAll("`", "'");
|
|
26
|
-
return `- \`${name}\` — ${singleLineLabel}`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function codeExecutionDescription(
|
|
30
|
-
candidates: readonly PiToolCandidate[],
|
|
31
|
-
): string {
|
|
32
|
-
const list = (
|
|
33
|
-
entries: readonly { readonly name: string; readonly label: string }[],
|
|
34
|
-
) => entries.length > 0
|
|
35
|
-
? entries.map(({ name, label }) => directoryEntry(name, label)).join("\n")
|
|
36
|
-
: "- None.";
|
|
37
|
-
|
|
38
|
-
return [
|
|
39
|
-
"Execute plain JavaScript in a sandbox using the exact Tool directory below.",
|
|
40
|
-
"",
|
|
41
|
-
"## `tools.*` Available",
|
|
42
|
-
list(candidates.map(({ tool }) => ({
|
|
43
|
-
name: tool.name,
|
|
44
|
-
label: tool.label ?? tool.name,
|
|
45
|
-
}))),
|
|
46
|
-
"",
|
|
47
|
-
"Call only the methods listed above through `tools.*`; never guess or construct a method name.",
|
|
48
|
-
"Use `codemode.describe(\"tools.method\")` when you need the exact input type for a listed method.",
|
|
49
|
-
"`codemode.search` cannot add methods to `tools.*` or load top-level Tools; it searches only connector methods and snippets already installed in this Code Mode Runtime.",
|
|
50
|
-
"Use `state.*` for the Workspace filesystem. Every method takes one object argument, for example `state.readFile({ path })` and `state.writeFile({ path, content })`.",
|
|
51
|
-
"Wrap raw fetch, random values, time, and other nondeterministic work in `codemode.step(name, fn)` so replay runs them once.",
|
|
52
|
-
"Some connector methods pause for approval and resume automatically. Do not re-issue paused code.",
|
|
53
|
-
"Keep all code outside connector calls and `codemode.step` deterministic.",
|
|
54
|
-
"Raw `fetch` is available inside `codemode.step(...)`; prefer connector SDKs when one owns the target.",
|
|
55
|
-
"There is no Node.js `require`, `process`, package manager, or Python runtime.",
|
|
56
|
-
].join("\n");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
14
|
/**
|
|
60
15
|
* 宿主的 Browser Rendering 绑定,只取本仓真正用到的那一面。
|
|
61
16
|
*
|
|
@@ -66,13 +21,6 @@ export interface RuntimeBrowserBinding {
|
|
|
66
21
|
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
67
22
|
}
|
|
68
23
|
|
|
69
|
-
/**
|
|
70
|
-
* 把宿主的 Worker Loader、出站网络和 Workspace 组装成代码执行 Port。
|
|
71
|
-
*
|
|
72
|
-
* Worker 宿主在具备 Durable Object state 和完整平台绑定时调用,然后把返回值交给 Runtime 工具组装。
|
|
73
|
-
*
|
|
74
|
-
* Think 的独立 execute factory 接受显式宿主参数,不要求 Agent 继承 Think;这里只借它组装 Codemode Runtime、Dynamic Worker executor 和已限定范围的 Workspace state connector。
|
|
75
|
-
*/
|
|
76
24
|
function result(details: unknown): AgentToolResult<unknown> {
|
|
77
25
|
return {
|
|
78
26
|
content: [{ type: "text", text: serializeOutput(details).text }],
|
|
@@ -80,56 +28,6 @@ function result(details: unknown): AgentToolResult<unknown> {
|
|
|
80
28
|
};
|
|
81
29
|
}
|
|
82
30
|
|
|
83
|
-
function sumUsage(results: readonly AgentToolResult<unknown>[]): Usage | undefined {
|
|
84
|
-
const usages = results.flatMap(({ usage }) => usage ? [usage] : []);
|
|
85
|
-
if (usages.length === 0) return undefined;
|
|
86
|
-
return usages.reduce<Usage>((total, usage) => ({
|
|
87
|
-
input: total.input + usage.input,
|
|
88
|
-
output: total.output + usage.output,
|
|
89
|
-
cacheRead: total.cacheRead + usage.cacheRead,
|
|
90
|
-
cacheWrite: total.cacheWrite + usage.cacheWrite,
|
|
91
|
-
...(total.cacheWrite1h === undefined && usage.cacheWrite1h === undefined
|
|
92
|
-
? {}
|
|
93
|
-
: { cacheWrite1h: (total.cacheWrite1h ?? 0) + (usage.cacheWrite1h ?? 0) }),
|
|
94
|
-
...(total.reasoning === undefined && usage.reasoning === undefined
|
|
95
|
-
? {}
|
|
96
|
-
: { reasoning: (total.reasoning ?? 0) + (usage.reasoning ?? 0) }),
|
|
97
|
-
totalTokens: total.totalTokens + usage.totalTokens,
|
|
98
|
-
cost: {
|
|
99
|
-
input: total.cost.input + usage.cost.input,
|
|
100
|
-
output: total.cost.output + usage.cost.output,
|
|
101
|
-
cacheRead: total.cost.cacheRead + usage.cost.cacheRead,
|
|
102
|
-
cacheWrite: total.cost.cacheWrite + usage.cost.cacheWrite,
|
|
103
|
-
total: total.cost.total + usage.cost.total,
|
|
104
|
-
},
|
|
105
|
-
}), {
|
|
106
|
-
input: 0,
|
|
107
|
-
output: 0,
|
|
108
|
-
cacheRead: 0,
|
|
109
|
-
cacheWrite: 0,
|
|
110
|
-
totalTokens: 0,
|
|
111
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function codeExecutionResult(
|
|
116
|
-
details: unknown,
|
|
117
|
-
innerResults: readonly AgentToolResult<unknown>[],
|
|
118
|
-
): AgentToolResult<unknown> {
|
|
119
|
-
const addedToolNames = [...new Set(
|
|
120
|
-
innerResults.flatMap(({ addedToolNames }) => addedToolNames ?? []),
|
|
121
|
-
)].sort();
|
|
122
|
-
const usage = sumUsage(innerResults);
|
|
123
|
-
return {
|
|
124
|
-
...result(details),
|
|
125
|
-
...(addedToolNames.length > 0 ? { addedToolNames } : {}),
|
|
126
|
-
...(usage ? { usage } : {}),
|
|
127
|
-
...(innerResults.length > 0 && innerResults.every(({ terminate }) => terminate === true)
|
|
128
|
-
? { terminate: true }
|
|
129
|
-
: {}),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
31
|
// 上游把 Code Mode 类工具交付为 AI SDK tool;本仓只取 description 和 execute 两件,
|
|
134
32
|
// 并在这里就地校验,使装配期缺件立刻失败,而不是等模型调用时才炸。
|
|
135
33
|
function toCodeExecutionPort(
|
|
@@ -195,7 +93,6 @@ function browserToolDescription(): string {
|
|
|
195
93
|
*
|
|
196
94
|
* Worker 宿主在具备 DO state 与 Browser 绑定时调用,返回值经 Platform Port 交给 Runtime 工具组装。
|
|
197
95
|
*
|
|
198
|
-
* 形状与 `createWorkspaceCodeExecutionFactory` 同构:宿主提供 DO state 与平台绑定,Runtime 只拿到一个可选装配输入。
|
|
199
96
|
* `create()` 延迟到 Tool Surface 真的要注册时才调,被 deny 的装配不会白建连接器。
|
|
200
97
|
*/
|
|
201
98
|
export function createBrowserExecutionFactory(options: {
|
|
@@ -240,42 +137,3 @@ export function createBrowserExecutionFactory(options: {
|
|
|
240
137
|
},
|
|
241
138
|
};
|
|
242
139
|
}
|
|
243
|
-
|
|
244
|
-
export function createWorkspaceCodeExecutionFactory(options: {
|
|
245
|
-
readonly ctx: DurableObjectState;
|
|
246
|
-
readonly loader: WorkerLoader;
|
|
247
|
-
readonly outbound: Fetcher;
|
|
248
|
-
readonly workspace: WorkspacePort;
|
|
249
|
-
}): RuntimeCodeExecutionFactory {
|
|
250
|
-
return {
|
|
251
|
-
create(candidates) {
|
|
252
|
-
const description = codeExecutionDescription(candidates);
|
|
253
|
-
return {
|
|
254
|
-
description,
|
|
255
|
-
async execute(input) {
|
|
256
|
-
const innerResults: AgentToolResult<unknown>[] = [];
|
|
257
|
-
const { tool } = createExecuteRuntime({
|
|
258
|
-
ctx: options.ctx,
|
|
259
|
-
loader: options.loader,
|
|
260
|
-
globalOutbound: options.outbound,
|
|
261
|
-
tools: piCandidatesToAiTools(candidates, {
|
|
262
|
-
onResult: (value) => innerResults.push(value),
|
|
263
|
-
}),
|
|
264
|
-
description,
|
|
265
|
-
// 先于外层 60s 截止结束,给 Runtime RPC 结算和 Worker 释放留出时间。
|
|
266
|
-
timeout: CODEMODE_SANDBOX_TIMEOUT_MS,
|
|
267
|
-
state: createWorkspaceStateBackend(
|
|
268
|
-
options.workspace as unknown as WorkspaceFsLike,
|
|
269
|
-
),
|
|
270
|
-
name: "execute",
|
|
271
|
-
});
|
|
272
|
-
return toCodeExecutionPort(
|
|
273
|
-
tool,
|
|
274
|
-
"execute",
|
|
275
|
-
(details) => codeExecutionResult(details, innerResults),
|
|
276
|
-
).execute(input);
|
|
277
|
-
},
|
|
278
|
-
};
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
}
|
package/src/pi/tool/core.ts
CHANGED
|
@@ -202,7 +202,7 @@ export function listExtensionsPiToolCandidate(
|
|
|
202
202
|
|
|
203
203
|
// #endregion
|
|
204
204
|
|
|
205
|
-
// #region Code Mode
|
|
205
|
+
// #region Browser Code Mode
|
|
206
206
|
|
|
207
207
|
const executeParameters = Type.Object({
|
|
208
208
|
code: Type.String({
|
|
@@ -213,9 +213,8 @@ const executeParameters = Type.Object({
|
|
|
213
213
|
});
|
|
214
214
|
const CODEMODE_EXECUTE_TIMEOUT_MS = 60_000;
|
|
215
215
|
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
// 避免两套心智模型;`label` 只用于超时文案,因为模型看到的名字由候选项决定。
|
|
216
|
+
// 把模型提供的浏览器代码交给 Codemode Runtime 执行,并在外层再压一道截止。
|
|
217
|
+
// `label` 只用于超时文案,因为模型看到的名字由候选项决定。
|
|
219
218
|
function runCodemode(
|
|
220
219
|
runtime: RuntimeCodeExecutionPort,
|
|
221
220
|
label: string,
|
|
@@ -254,36 +253,6 @@ function runCodemode(
|
|
|
254
253
|
};
|
|
255
254
|
}
|
|
256
255
|
|
|
257
|
-
/**
|
|
258
|
-
* 把 Cloudflare Codemode Runtime handle 包装为 Pi 代码执行工具候选项。
|
|
259
|
-
*
|
|
260
|
-
* Tool Surface 收到 Host 已组装的 Code Execution Port 后调用,
|
|
261
|
-
* 模型再通过 `execute` 运行代码。
|
|
262
|
-
*
|
|
263
|
-
* Code Mode 作为一个完整的 safe 工具对外暴露,内部能力不再单独提权。
|
|
264
|
-
*/
|
|
265
|
-
export function codeExecutionPiToolCandidate(
|
|
266
|
-
runtime: RuntimeCodeExecutionPort,
|
|
267
|
-
): PiToolCandidate {
|
|
268
|
-
const tool: AgentTool<typeof executeParameters> = {
|
|
269
|
-
name: "execute",
|
|
270
|
-
label: "Execute JavaScript",
|
|
271
|
-
description: runtime.description,
|
|
272
|
-
parameters: executeParameters,
|
|
273
|
-
// Pi 工具循环在模型选择 `execute` 时调用,调用前允许 Turn 取消。
|
|
274
|
-
// 必须通过 Runtime handle 而不是直接调用 executor,因为 Cloudflare Codemode 把重放、审批和执行日志放在持久化 Runtime 层。
|
|
275
|
-
execute: runCodemode(runtime, "Code Mode execute"),
|
|
276
|
-
};
|
|
277
|
-
return {
|
|
278
|
-
owner: "core:codemode",
|
|
279
|
-
requiredExecutionLevel: "safe",
|
|
280
|
-
outputBudget: { kind: "structure" },
|
|
281
|
-
source: "codemode",
|
|
282
|
-
summary: "Run JavaScript with network and configured connector access",
|
|
283
|
-
tool,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
256
|
/** 模型可见的浏览器工具名;属外部契约,改名是破坏性变更。 */
|
|
288
257
|
export const BROWSER_EXECUTE_TOOL_NAME = "browser_execute";
|
|
289
258
|
|
package/src/pi/tool/declared.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface PiDeclaredToolPolicy<Reply = unknown> {
|
|
|
49
49
|
readonly modelName: string;
|
|
50
50
|
readonly requiredExecutionLevel: PiToolCandidate["requiredExecutionLevel"];
|
|
51
51
|
readonly requiredExecutionLevelForInput?: PiToolCandidate["requiredExecutionLevelForInput"];
|
|
52
|
-
readonly
|
|
52
|
+
readonly retry?: PiToolCandidate["retry"];
|
|
53
53
|
readonly source?: PiToolCandidate["source"];
|
|
54
54
|
readonly summary?: string;
|
|
55
55
|
/**
|
|
@@ -115,7 +115,7 @@ export function createPiDeclaredToolCandidate<Reply = unknown>(
|
|
|
115
115
|
...(policy.requiredExecutionLevelForInput
|
|
116
116
|
? { requiredExecutionLevelForInput: policy.requiredExecutionLevelForInput }
|
|
117
117
|
: {}),
|
|
118
|
-
...(policy.
|
|
118
|
+
...(policy.retry ? { retry: policy.retry } : {}),
|
|
119
119
|
...(policy.source ? { source: policy.source } : {}),
|
|
120
120
|
};
|
|
121
121
|
}
|
package/src/pi/tool/schedule.ts
CHANGED
|
@@ -92,7 +92,7 @@ function candidate<T extends TSchema>(
|
|
|
92
92
|
options: Partial<
|
|
93
93
|
Pick<
|
|
94
94
|
PiToolCandidate,
|
|
95
|
-
"alwaysRequiresApproval" | "
|
|
95
|
+
"alwaysRequiresApproval" | "owner" | "requiredExecutionLevel" | "summary"
|
|
96
96
|
>
|
|
97
97
|
> = {},
|
|
98
98
|
): PiToolCandidate {
|
|
@@ -104,7 +104,6 @@ function candidate<T extends TSchema>(
|
|
|
104
104
|
...(options.alwaysRequiresApproval
|
|
105
105
|
? { alwaysRequiresApproval: true }
|
|
106
106
|
: {}),
|
|
107
|
-
...(options.direct ? { direct: true } : {}),
|
|
108
107
|
...(options.summary ? { summary: options.summary } : {}),
|
|
109
108
|
};
|
|
110
109
|
}
|
|
@@ -134,7 +133,6 @@ export function schedulePiToolCandidates(
|
|
|
134
133
|
},
|
|
135
134
|
{
|
|
136
135
|
alwaysRequiresApproval: true,
|
|
137
|
-
direct: true,
|
|
138
136
|
summary: "Create a scheduled task",
|
|
139
137
|
},
|
|
140
138
|
),
|
package/src/pi/tool/skill.ts
CHANGED
|
@@ -93,7 +93,7 @@ function budgetSkillResources(skill: LoadedSkill): LoadedSkill {
|
|
|
93
93
|
if (portable.length > 0) {
|
|
94
94
|
lines.push(
|
|
95
95
|
`> If any of these resources are needed, copy all required ones into the Workspace ` +
|
|
96
|
-
`in one
|
|
96
|
+
`in one materialize_skill_resource call using its resources array: ` +
|
|
97
97
|
`${portable.map((entry) => entry.path).join(", ")}.`,
|
|
98
98
|
);
|
|
99
99
|
}
|
|
@@ -175,6 +175,21 @@ const SKILL_TOOL_LABELS: Readonly<Record<string, string>> = {
|
|
|
175
175
|
materialize_skill_resource: "Materialize Skill resource",
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
+
const SKILL_TOOL_PARAMETER_DESCRIPTIONS: Readonly<Record<string, Readonly<Record<string, string>>>> = {
|
|
179
|
+
activate_skill: {
|
|
180
|
+
name: "Exact name of the available Skill to activate.",
|
|
181
|
+
},
|
|
182
|
+
read_skill_resource: {
|
|
183
|
+
name: "Name of the activated Skill. Omit only when path starts with the Skill name.",
|
|
184
|
+
path: "Bundled resource path listed by activate_skill.",
|
|
185
|
+
},
|
|
186
|
+
run_skill_script: {
|
|
187
|
+
name: "Name of the activated Skill that supplies the script.",
|
|
188
|
+
path: "Bundled script path listed by activate_skill.",
|
|
189
|
+
input: "JSON input expected by the Skill script. Defaults to an empty object.",
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
178
193
|
const SKILL_ENTRY_READ_GUIDANCE =
|
|
179
194
|
"SKILL.md contains the Skill instructions; use activate_skill instead.";
|
|
180
195
|
|
|
@@ -196,44 +211,95 @@ function materializeSkillResourceTool(
|
|
|
196
211
|
) {
|
|
197
212
|
const names = bindings.map(({ name }) => name) as [string, ...string[]];
|
|
198
213
|
const byName = new Map(bindings.map((binding) => [binding.name, binding]));
|
|
214
|
+
const resourceInput = z.object({
|
|
215
|
+
name: z.enum(names).describe("Activated Skill name"),
|
|
216
|
+
path: z.string().min(1).describe("Bundled resource path listed by activate_skill"),
|
|
217
|
+
destination: z.string().min(1).startsWith("/").describe(
|
|
218
|
+
"Absolute destination path in the Workspace",
|
|
219
|
+
),
|
|
220
|
+
});
|
|
199
221
|
return tool({
|
|
200
222
|
description:
|
|
201
|
-
"Copy
|
|
223
|
+
"Copy complete bundled Skill resources directly into the Workspace without returning their contents to the model. " +
|
|
202
224
|
"Use this instead of read_skill_resource when a template, script, image, font, or other asset must become a Workspace file. " +
|
|
203
|
-
"
|
|
225
|
+
"Pass every required item in the resources array; items are copied in order and failures are reported per item. " +
|
|
204
226
|
"The destination is created or overwritten, including parent directories.",
|
|
205
227
|
inputSchema: z.object({
|
|
206
|
-
|
|
207
|
-
path: z.string().min(1).describe("Bundled resource path listed by activate_skill"),
|
|
208
|
-
destination: z.string().min(1).describe("Absolute destination path in the Workspace"),
|
|
228
|
+
resources: z.array(resourceInput).min(1).max(100),
|
|
209
229
|
}),
|
|
210
|
-
execute: async ({
|
|
230
|
+
execute: async ({ resources }, { abortSignal }) => {
|
|
211
231
|
abortSignal?.throwIfAborted();
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
232
|
+
const destinations = new Set<string>();
|
|
233
|
+
for (const { destination } of resources) {
|
|
234
|
+
if (destinations.has(destination)) {
|
|
235
|
+
throw new Error(`Duplicate materialize destination: ${destination}`);
|
|
236
|
+
}
|
|
237
|
+
destinations.add(destination);
|
|
215
238
|
}
|
|
216
|
-
const resource = await source.readResource(name, path);
|
|
217
|
-
if (!resource) throw new Error(`Resource not found: ${name}/${path}`);
|
|
218
|
-
abortSignal?.throwIfAborted();
|
|
219
|
-
const bytes = resourceBytes(resource);
|
|
220
239
|
|
|
221
|
-
|
|
240
|
+
const results: Array<Record<string, unknown>> = [];
|
|
241
|
+
for (const { name, path, destination } of resources) {
|
|
222
242
|
abortSignal?.throwIfAborted();
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
|
|
243
|
+
const source = byName.get(name)?.source;
|
|
244
|
+
if (!source?.readResource) {
|
|
245
|
+
results.push({
|
|
246
|
+
status: "error",
|
|
247
|
+
name,
|
|
248
|
+
path,
|
|
249
|
+
destination,
|
|
250
|
+
error: `Skill \"${name}\" has no readable resources.`,
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const resource = await source.readResource(name, path);
|
|
256
|
+
if (!resource) {
|
|
257
|
+
results.push({
|
|
258
|
+
status: "error",
|
|
259
|
+
name,
|
|
260
|
+
path,
|
|
261
|
+
destination,
|
|
262
|
+
error: `Resource not found: ${name}/${path}`,
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
abortSignal?.throwIfAborted();
|
|
267
|
+
const bytes = resourceBytes(resource);
|
|
268
|
+
|
|
269
|
+
await serializeWorkspaceMutation(workspace, destination, async () => {
|
|
270
|
+
abortSignal?.throwIfAborted();
|
|
271
|
+
const parent = destination.replace(/\/[^/]+$/, "");
|
|
272
|
+
if (parent && parent !== "/") {
|
|
273
|
+
await workspace.mkdir(parent, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
await workspace.writeFileBytes(destination, bytes, resource.mimeType);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
results.push({
|
|
279
|
+
status: "written",
|
|
280
|
+
name,
|
|
281
|
+
path: resource.path,
|
|
282
|
+
destination,
|
|
283
|
+
bytesWritten: bytes.byteLength,
|
|
284
|
+
encoding: resource.encoding ?? "text",
|
|
285
|
+
...(resource.mimeType ? { mimeType: resource.mimeType } : {}),
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
abortSignal?.throwIfAborted();
|
|
289
|
+
results.push({
|
|
290
|
+
status: "error",
|
|
291
|
+
name,
|
|
292
|
+
path,
|
|
293
|
+
destination,
|
|
294
|
+
error: "Resource materialization failed",
|
|
295
|
+
});
|
|
226
296
|
}
|
|
227
|
-
|
|
228
|
-
});
|
|
297
|
+
}
|
|
229
298
|
|
|
230
299
|
return {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
bytesWritten: bytes.byteLength,
|
|
235
|
-
encoding: resource.encoding ?? "text",
|
|
236
|
-
...(resource.mimeType ? { mimeType: resource.mimeType } : {}),
|
|
300
|
+
written: results.filter(({ status }) => status === "written").length,
|
|
301
|
+
failed: results.filter(({ status }) => status === "error").length,
|
|
302
|
+
results,
|
|
237
303
|
};
|
|
238
304
|
},
|
|
239
305
|
});
|
|
@@ -270,6 +336,17 @@ export async function skillPiToolCandidates(
|
|
|
270
336
|
}
|
|
271
337
|
: {}),
|
|
272
338
|
});
|
|
339
|
+
const properties = (adapted.parameters as { properties?: unknown }).properties;
|
|
340
|
+
if (properties && typeof properties === "object" && !Array.isArray(properties)) {
|
|
341
|
+
for (const [parameter, description] of Object.entries(
|
|
342
|
+
SKILL_TOOL_PARAMETER_DESCRIPTIONS[name] ?? {},
|
|
343
|
+
)) {
|
|
344
|
+
const schema = (properties as Record<string, unknown>)[parameter];
|
|
345
|
+
if (schema && typeof schema === "object" && !Array.isArray(schema)) {
|
|
346
|
+
(schema as Record<string, unknown>).description ??= description;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
273
350
|
if (name === "read_skill_resource") {
|
|
274
351
|
const execute = adapted.execute;
|
|
275
352
|
adapted.execute = (toolCallId, input, signal) => {
|
|
@@ -303,9 +380,6 @@ export async function skillPiToolCandidates(
|
|
|
303
380
|
...(name === "activate_skill"
|
|
304
381
|
? { outputBudget: { kind: "structure" as const } }
|
|
305
382
|
: {}),
|
|
306
|
-
...(name === "materialize_skill_resource"
|
|
307
|
-
? { codeExecutionOnly: true as const }
|
|
308
|
-
: {}),
|
|
309
383
|
tool: adapted,
|
|
310
384
|
};
|
|
311
385
|
});
|
|
@@ -70,9 +70,16 @@ const workspaceReadParameters = Type.Object({
|
|
|
70
70
|
})),
|
|
71
71
|
});
|
|
72
72
|
const workspaceEditParameters = Type.Object({
|
|
73
|
-
path: Type.String({
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
path: Type.String({
|
|
74
|
+
minLength: 1,
|
|
75
|
+
maxLength: 4_096,
|
|
76
|
+
description: "Absolute Workspace path of the file to edit.",
|
|
77
|
+
}),
|
|
78
|
+
old_string: Type.String({
|
|
79
|
+
description:
|
|
80
|
+
"Exact existing text to replace. Include enough surrounding context to match one location.",
|
|
81
|
+
}),
|
|
82
|
+
new_string: Type.String({ description: "Replacement text." }),
|
|
76
83
|
});
|
|
77
84
|
|
|
78
85
|
/**
|
|
@@ -430,24 +437,47 @@ export function workspacePiToolCandidates(
|
|
|
430
437
|
// #region Sandbox tools
|
|
431
438
|
|
|
432
439
|
const sandboxCommandParameters = {
|
|
433
|
-
command: Type.String({
|
|
434
|
-
|
|
435
|
-
|
|
440
|
+
command: Type.String({
|
|
441
|
+
minLength: 1,
|
|
442
|
+
maxLength: 32_768,
|
|
443
|
+
description: "Shell command to run in the isolated Linux Sandbox.",
|
|
444
|
+
}),
|
|
445
|
+
cwd: Type.Optional(Type.String({
|
|
446
|
+
maxLength: 4_096,
|
|
447
|
+
description: "Sandbox working directory. Omit to use the Sandbox default.",
|
|
448
|
+
})),
|
|
449
|
+
stdin: Type.Optional(Type.String({
|
|
450
|
+
maxLength: 65_536,
|
|
451
|
+
description: "Optional text to pass to the command on standard input.",
|
|
452
|
+
})),
|
|
436
453
|
};
|
|
437
454
|
const sandboxExecParameters = Type.Object({
|
|
438
455
|
...sandboxCommandParameters,
|
|
439
456
|
timeoutMs: Type.Optional(
|
|
440
|
-
Type.Integer({
|
|
457
|
+
Type.Integer({
|
|
458
|
+
minimum: 1,
|
|
459
|
+
maximum: 60_000,
|
|
460
|
+
description: "Maximum command runtime in milliseconds, from 1 to 60000.",
|
|
461
|
+
}),
|
|
441
462
|
),
|
|
442
463
|
});
|
|
443
464
|
const sandboxStartParameters = Type.Object(sandboxCommandParameters);
|
|
444
465
|
const sandboxProcessParameters = Type.Object({
|
|
445
|
-
id: Type.String({
|
|
466
|
+
id: Type.String({
|
|
467
|
+
minLength: 1,
|
|
468
|
+
maxLength: 128,
|
|
469
|
+
description: "Process ID returned by sandbox_start_process.",
|
|
470
|
+
}),
|
|
446
471
|
});
|
|
447
472
|
const sandboxPublishParameters = Type.Object({
|
|
448
|
-
paths: Type.Array(Type.String({
|
|
473
|
+
paths: Type.Array(Type.String({
|
|
474
|
+
minLength: 1,
|
|
475
|
+
maxLength: 4_096,
|
|
476
|
+
description: "Sandbox file path to publish.",
|
|
477
|
+
}), {
|
|
449
478
|
minItems: 1,
|
|
450
479
|
maxItems: 100,
|
|
480
|
+
description: "Files to copy from the temporary Sandbox into the persistent Workspace.",
|
|
451
481
|
}),
|
|
452
482
|
});
|
|
453
483
|
|
package/src/runtime-agent.ts
CHANGED
|
@@ -672,9 +672,6 @@ export function defineRuntimeAgent<
|
|
|
672
672
|
...(assembly.bindings?.workspace
|
|
673
673
|
? { workspace: assembly.bindings.workspace }
|
|
674
674
|
: {}),
|
|
675
|
-
...(assembly.bindings?.codeExecution
|
|
676
|
-
? { codeExecution: assembly.bindings.codeExecution }
|
|
677
|
-
: {}),
|
|
678
675
|
},
|
|
679
676
|
memoryProfile: {
|
|
680
677
|
enabled: false,
|