@springbrand/agent-runtime 0.3.0-alpha.1 → 0.3.0-alpha.10
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 +6 -6
- package/src/adapter/cloudflare/agent.ts +157 -14
- package/src/adapter/cloudflare/define-agent.ts +12 -7
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/adapter/cloudflare/schema.ts +15 -0
- package/src/adapter/cloudflare/subagent/definition.ts +4 -4
- package/src/adapter/cloudflare/subagent/runner.ts +1 -1
- package/src/adapter/cloudflare/subagent/tools.ts +2 -2
- package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
- package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
- package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
- package/src/config/agent-context.ts +1 -1
- package/src/config/assembler.ts +29 -31
- package/src/config/assembly-snapshot.ts +1 -1
- package/src/config/definition.ts +1 -1
- package/src/config/harness-runtime.ts +9 -1
- package/src/config/model-guidance.ts +21 -0
- package/src/config/pinning.ts +22 -3
- package/src/config/ports.ts +27 -7
- package/src/config/public-contracts.ts +2 -2
- package/src/context/assembly.ts +1 -1
- package/src/context/extension/assembly.ts +2 -2
- package/src/context/memory/budget/gate.ts +19 -3
- package/src/conversation/harness-stream.ts +2 -1
- package/src/conversation/message/projection.ts +38 -21
- package/src/conversation/state.ts +23 -2
- package/src/harness/session-harness.ts +86 -5
- package/src/index.ts +7 -7
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/prompt.ts +7 -6
- package/src/lib/token-estimate.ts +8 -0
- package/src/models/index.ts +1 -0
- package/src/session/cloudflare/migration.ts +70 -6
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +12 -1
- package/src/submission/contracts.ts +42 -2
- package/src/submission/delivery.ts +1 -1
- package/src/submission/finalization.ts +1 -1
- package/src/submission/repository.ts +64 -4
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/coordinator.ts +1 -1
- package/src/telemetry/subagent-lifecycle.ts +1 -1
- package/src/telemetry/turn-events.ts +1 -1
- package/src/tools/README.md +44 -0
- package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
- package/src/tools/{declared.ts → adapters/declared.ts} +6 -2
- package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +2 -2
- package/src/tools/assistant/ask-user.ts +317 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/tools/{time.ts → assistant/time.ts} +1 -1
- package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -49
- package/src/tools/{core.ts → codemode/index.ts} +36 -92
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +28 -26
- package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
- package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
- package/src/tools/{approval → runtime/approval}/repository.ts +13 -12
- package/src/tools/{compiler.ts → runtime/compiler.ts} +84 -147
- package/src/tools/runtime/contracts.ts +117 -0
- package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +147 -37
- package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
- package/src/tools/{interaction → runtime/interaction}/repository.ts +1 -7
- package/src/tools/runtime/result.ts +22 -0
- package/src/tools/runtime/unattended-error.ts +4 -0
- package/src/tools/sandbox/index.ts +228 -0
- package/src/tools/schedule/index.ts +301 -0
- package/src/tools/skill/contracts.ts +20 -0
- package/src/tools/skill/index.ts +99 -0
- package/src/tools/skill/resources.ts +322 -0
- package/src/tools/skill/scripts.ts +168 -0
- package/src/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
- package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
- package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
- package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
- package/src/tools/{temporary-agent → subagent/temporary}/core.ts +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
- package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1320 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
- package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
- package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
- package/src/tools/base.ts +0 -534
- package/src/tools/schedule.ts +0 -261
- package/src/tools/skill.ts +0 -565
- package/src/tools/web-search/api.ts +0 -1318
- package/src/tools/web-search/index.ts +0 -2
- package/src/tools/workspace-state-connector.ts +0 -146
- package/src/tools/{approval → runtime/approval}/index.ts +0 -0
- package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
- package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
- /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
|
@@ -1,38 +1,31 @@
|
|
|
1
|
+
import { truncateResponse, truncateResult, type ProxyToolOutput } from "@cloudflare/codemode";
|
|
2
|
+
import { generateTypes } from "@cloudflare/codemode/ai";
|
|
1
3
|
import {
|
|
2
|
-
createWorkspaceStateBackend,
|
|
3
4
|
STATE_TYPES,
|
|
5
|
+
createWorkspaceStateBackend,
|
|
4
6
|
type StateBackend,
|
|
5
7
|
type WorkspaceFsLike,
|
|
6
8
|
} from "@cloudflare/shell";
|
|
7
|
-
import {
|
|
8
|
-
truncateResponse,
|
|
9
|
-
truncateResult,
|
|
10
|
-
type ProxyToolOutput,
|
|
11
|
-
} from "@cloudflare/codemode";
|
|
12
9
|
import { createBrowserTools } from "@cloudflare/think/tools/browser";
|
|
13
|
-
import {
|
|
14
|
-
createExecuteRuntime,
|
|
15
|
-
type CreateExecuteToolOptions,
|
|
16
|
-
} from "@cloudflare/think/tools/execute";
|
|
10
|
+
import { createExecuteRuntime } from "@cloudflare/think/tools/execute";
|
|
17
11
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
18
12
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
19
13
|
import type { ToolSet } from "ai";
|
|
20
14
|
import type {
|
|
21
15
|
RuntimeBrowserPort,
|
|
16
|
+
RuntimeCodeExecutionFactory,
|
|
22
17
|
RuntimeCodeExecutionPort,
|
|
23
18
|
WorkspacePort,
|
|
24
|
-
} from "
|
|
19
|
+
} from "../../config/ports";
|
|
20
|
+
import { serializeOutput } from "../../lib/artifacts";
|
|
21
|
+
import { piCandidatesToAiTools } from "../adapters/pi-to-ai";
|
|
25
22
|
import {
|
|
26
|
-
WorkspaceStateConnector,
|
|
27
23
|
WORKSPACE_PAGING_INSTRUCTIONS,
|
|
28
24
|
WORKSPACE_STATE_TYPES,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import { serializeOutput } from "../lib/artifacts";
|
|
32
|
-
import type { PiToolCandidate } from "./compiler";
|
|
33
|
-
import { piCandidatesToAiTools } from "./nested-tools";
|
|
25
|
+
WorkspaceStateConnector,
|
|
26
|
+
} from "./workspace-connector";
|
|
34
27
|
|
|
35
|
-
//
|
|
28
|
+
// 将 Cloudflare 的 Code Mode 与浏览器执行能力适配为 Runtime Port;Pi Tool 定义位于同目录 index.ts。
|
|
36
29
|
|
|
37
30
|
const CODEMODE_SANDBOX_TIMEOUT_MS = 55_000;
|
|
38
31
|
export type WorkspaceAccessMode = "none" | "read" | "write";
|
|
@@ -103,7 +96,7 @@ When an asset already exists in Workspace, use state.cp to copy it into the curr
|
|
|
103
96
|
|
|
104
97
|
Never create or write data URLs, base64 image strings, or embedded binary payloads in Code Mode.
|
|
105
98
|
|
|
106
|
-
Copy a remote asset into Workspace with tools.materialize_url({ url, destination }).
|
|
99
|
+
Copy a remote asset into Workspace with tools.materialize_url({ url, destination }). Do not carry large responses, binary payloads, data URLs, or base64 assets through Code Mode.
|
|
107
100
|
|
|
108
101
|
After materialization, reference the Workspace file by a relative path. Website HTML and CSS must reference relative Workspace assets, never temporary external URLs or data URLs.
|
|
109
102
|
|
|
@@ -193,12 +186,12 @@ function projectCodeModeResult(details: unknown): unknown {
|
|
|
193
186
|
const calls = Array.isArray(output.calls) ? output.calls : [];
|
|
194
187
|
const callSummary = calls.length > 0
|
|
195
188
|
? {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
189
|
+
total: calls.length,
|
|
190
|
+
failed: calls.filter(({ state }) => state === "error").length,
|
|
191
|
+
methods: [...new Set(calls.map(({ method }) => method))]
|
|
192
|
+
.map((method) => truncateResponse(method, { maxChars: 256 }))
|
|
193
|
+
.slice(0, 100),
|
|
194
|
+
}
|
|
202
195
|
: undefined;
|
|
203
196
|
const common = {
|
|
204
197
|
status: output.status,
|
|
@@ -224,7 +217,7 @@ function projectCodeModeResult(details: unknown): unknown {
|
|
|
224
217
|
// 上游把 Code Mode 类工具交付为 AI SDK tool;本仓只取 description 和 execute 两件,
|
|
225
218
|
// 并在这里就地校验,使装配期缺件立刻失败,而不是等模型调用时才炸。
|
|
226
219
|
function toCodeExecutionPort(
|
|
227
|
-
tool: ToolSet[string],
|
|
220
|
+
tool: Pick<ToolSet[string], "description" | "execute">,
|
|
228
221
|
name: string,
|
|
229
222
|
project: (details: unknown) => AgentToolResult<unknown> = result,
|
|
230
223
|
): RuntimeCodeExecutionPort {
|
|
@@ -345,35 +338,40 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
345
338
|
options.workspace,
|
|
346
339
|
options.workspaceAccessMode,
|
|
347
340
|
);
|
|
348
|
-
const workspaceState = state
|
|
341
|
+
const workspaceState = state
|
|
349
342
|
? new WorkspaceStateConnector(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
},
|
|
355
|
-
)
|
|
343
|
+
options.ctx,
|
|
344
|
+
state,
|
|
345
|
+
options.workspace,
|
|
346
|
+
)
|
|
356
347
|
: undefined;
|
|
357
348
|
const createPort = (
|
|
358
349
|
innerResults: AgentToolResult<unknown>[],
|
|
359
|
-
stateOptions: Pick<CreateExecuteToolOptions, "state" | "connectors">,
|
|
360
350
|
) => {
|
|
361
351
|
let nestedCallOrdinal = 0;
|
|
362
|
-
const
|
|
352
|
+
const tools = piCandidatesToAiTools(candidates, {
|
|
353
|
+
fallbackToolCallId: (name) =>
|
|
354
|
+
`codemode:${++nestedCallOrdinal}:${name}`,
|
|
355
|
+
onResult: (value) => innerResults.push(value),
|
|
356
|
+
});
|
|
357
|
+
const { tool, runtime } = createExecuteRuntime({
|
|
363
358
|
ctx: options.ctx,
|
|
364
359
|
loader: options.loader,
|
|
365
360
|
globalOutbound: options.outbound,
|
|
366
|
-
tools
|
|
367
|
-
fallbackToolCallId: (name) =>
|
|
368
|
-
`codemode:${++nestedCallOrdinal}:${name}`,
|
|
369
|
-
onResult: (value) => innerResults.push(value),
|
|
370
|
-
}),
|
|
361
|
+
tools,
|
|
371
362
|
timeout: CODEMODE_SANDBOX_TIMEOUT_MS,
|
|
372
|
-
|
|
363
|
+
connectors: workspaceState ? [workspaceState] : [],
|
|
373
364
|
name: "execute",
|
|
374
365
|
});
|
|
366
|
+
// Generate the official description from the same connectors used to execute.
|
|
367
|
+
const description = runtime.tool({
|
|
368
|
+
connectorHints: {
|
|
369
|
+
tools: "host tools as async functions; call tools.<method>({ ... }) inside execute. Discover additional methods with codemode.search and codemode.describe.",
|
|
370
|
+
state: "the workspace filesystem. Every method takes ONE object argument: state.readFile({ path }), state.writeFile({ path, content }), state.readdir({ path }).",
|
|
371
|
+
}
|
|
372
|
+
}).description;
|
|
375
373
|
return toCodeExecutionPort(
|
|
376
|
-
tool,
|
|
374
|
+
{ ...tool, description },
|
|
377
375
|
"execute",
|
|
378
376
|
(details) => codeExecutionResult(
|
|
379
377
|
projectCodeModeResult(details),
|
|
@@ -381,21 +379,24 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
381
379
|
),
|
|
382
380
|
);
|
|
383
381
|
};
|
|
384
|
-
//
|
|
385
|
-
const
|
|
382
|
+
// External declarations opt out of constrained sampling and remain discoverable on demand.
|
|
383
|
+
const inlineCandidates = candidates.filter(({ tool }) => tool.constrainedSampling !== false);
|
|
384
|
+
const toolTypes = inlineCandidates.length > 0
|
|
385
|
+
? generateTypes(piCandidatesToAiTools(inlineCandidates, {}), "tools")
|
|
386
|
+
: undefined;
|
|
387
|
+
const description = createPort([]).description;
|
|
386
388
|
return {
|
|
387
389
|
description: [
|
|
388
390
|
description,
|
|
391
|
+
...(toolTypes ? ["## Code Mode Tool types\nUse these signatures inside execute. The Tool routing list defines which methods also have a top-level entry.\n\n```ts\n" + toolTypes + "\n```"] : []),
|
|
389
392
|
...(state ? [
|
|
390
393
|
"## Workspace type reference\nOnly methods exposed by connector discovery are callable.\n\n```ts\n" +
|
|
391
|
-
|
|
394
|
+
(options.workspace.readFilePage ? WORKSPACE_STATE_TYPES : STATE_TYPES) + "\n```",
|
|
392
395
|
] : []),
|
|
393
|
-
...(
|
|
396
|
+
...(state && options.workspace.readFilePage ? [WORKSPACE_PAGING_INSTRUCTIONS] : []),
|
|
394
397
|
SPRINGBRAND_CODE_MODE_POLICY,
|
|
395
398
|
].join("\n\n"),
|
|
396
|
-
execute: (input) => createPort(
|
|
397
|
-
[], workspaceState ? { connectors: [workspaceState] } : state ? { state } : {},
|
|
398
|
-
).execute(input),
|
|
399
|
+
execute: (input) => createPort([]).execute(input),
|
|
399
400
|
};
|
|
400
401
|
},
|
|
401
402
|
};
|
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentTool,
|
|
3
|
-
AgentToolResult,
|
|
4
|
-
} from "@earendil-works/pi-agent-core";
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
2
|
import { Type } from "@earendil-works/pi-ai";
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
} from "../
|
|
9
|
-
import {
|
|
10
|
-
import type { PiLoadedExtension } from "../context/extension/assembly";
|
|
11
|
-
import type { PiToolCandidate } from "./compiler";
|
|
12
|
-
|
|
13
|
-
// 本文件沿用 `../../index.ts` 入口定义的 Extension、Port 和 Tool Candidate 术语。
|
|
14
|
-
|
|
15
|
-
// 把核心工具的结构化值投影为 Pi 同时需要的文本和 details。
|
|
16
|
-
// 本文件中所有核心工具 execute 方法在返回宿主结果时调用它。
|
|
17
|
-
// 统一使用 `serializeOutput()` 可以保持模型可见文本和持久化 details 来自同一个值。
|
|
18
|
-
function result(details: unknown): AgentToolResult<unknown> {
|
|
19
|
-
return {
|
|
20
|
-
content: [{ type: "text", text: serializeOutput(details).text }],
|
|
21
|
-
details,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
3
|
+
import type { RuntimeCodeExecutionPort } from "../../config/ports";
|
|
4
|
+
import { serializeOutput } from "../../lib/artifacts";
|
|
5
|
+
import type { PiToolCandidate } from "../runtime/contracts";
|
|
6
|
+
import { result } from "../runtime/result";
|
|
24
7
|
|
|
25
8
|
const BROWSER_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
9
|
+
|
|
26
10
|
const BROWSER_IMAGE_MAX_BASE64_CHARS = Math.ceil(BROWSER_IMAGE_MAX_BYTES / 3) * 4;
|
|
11
|
+
|
|
27
12
|
const BROWSER_IMAGE_MIME_TYPES = new Set([
|
|
28
13
|
"image/jpeg",
|
|
29
14
|
"image/png",
|
|
@@ -87,7 +72,7 @@ function browserResult(details: unknown): AgentToolResult<unknown> {
|
|
|
87
72
|
|
|
88
73
|
const calls = Array.isArray(outer.calls) ? outer.calls : [];
|
|
89
74
|
let screenshotCall: Record<string, unknown> | undefined;
|
|
90
|
-
for (let index = calls.length - 1;
|
|
75
|
+
for (let index = calls.length - 1;index >= 0;index -= 1) {
|
|
91
76
|
const call = record(calls[index]);
|
|
92
77
|
const args = record(call?.args);
|
|
93
78
|
if (
|
|
@@ -117,39 +102,39 @@ function browserResult(details: unknown): AgentToolResult<unknown> {
|
|
|
117
102
|
|
|
118
103
|
const projectedCalls = Array.isArray(outer.calls)
|
|
119
104
|
? outer.calls.map((call) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
105
|
+
const entry = record(call);
|
|
106
|
+
const args = record(entry?.args);
|
|
107
|
+
const callResult = record(entry?.result);
|
|
108
|
+
if (
|
|
109
|
+
entry?.connector !== "cdp" ||
|
|
110
|
+
entry.method !== "send" ||
|
|
111
|
+
args?.method !== "Page.captureScreenshot" ||
|
|
112
|
+
typeof callResult?.data !== "string"
|
|
113
|
+
) return call;
|
|
114
|
+
const callFormat = record(args.params)?.format;
|
|
115
|
+
return {
|
|
116
|
+
...entry,
|
|
117
|
+
result: {
|
|
118
|
+
...callResult,
|
|
119
|
+
data: browserImageMetadata(
|
|
120
|
+
callResult.data,
|
|
121
|
+
screenshotMimeType(callFormat),
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
})
|
|
141
126
|
: outer.calls;
|
|
142
127
|
const projectedDetails = {
|
|
143
128
|
...outer,
|
|
144
129
|
result: inner
|
|
145
130
|
? {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
...inner,
|
|
132
|
+
image: projectedImage,
|
|
133
|
+
}
|
|
149
134
|
: {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
image: projectedImage,
|
|
136
|
+
note: "Screenshot recovered from the Page.captureScreenshot result.",
|
|
137
|
+
},
|
|
153
138
|
...("calls" in outer ? { calls: projectedCalls } : {}),
|
|
154
139
|
};
|
|
155
140
|
return {
|
|
@@ -163,47 +148,7 @@ function browserResult(details: unknown): AgentToolResult<unknown> {
|
|
|
163
148
|
};
|
|
164
149
|
}
|
|
165
150
|
|
|
166
|
-
// #region Extension discovery
|
|
167
|
-
|
|
168
|
-
const noParameters = Type.Object({}, { additionalProperties: false });
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* 为当前已加载 Extension 列表创建一个 Pi 工具候选项。
|
|
172
|
-
*
|
|
173
|
-
* Runtime 组装层在 Extension 装载完成后调用,模型需要发现当前可用扩展时使用该工具。
|
|
174
|
-
*
|
|
175
|
-
* 候选项捕获准备阶段的只读列表,这与 pinned Runtime 组装保持一致,不能在执行时重新扫描出另一份 Extension 集合。
|
|
176
|
-
*/
|
|
177
|
-
export function listExtensionsPiToolCandidate(
|
|
178
|
-
extensions: readonly PiLoadedExtension[],
|
|
179
|
-
): PiToolCandidate {
|
|
180
|
-
const tool: AgentTool<typeof noParameters> = {
|
|
181
|
-
name: "list_extensions",
|
|
182
|
-
label: "List extensions",
|
|
183
|
-
description: "List all currently loaded extensions and their tools.",
|
|
184
|
-
parameters: noParameters,
|
|
185
|
-
// 返回这个 pinned Runtime 已加载的 Extension 快照。
|
|
186
|
-
// Pi 工具循环在模型选择 `list_extensions` 时调用,调用前允许 Turn 取消。
|
|
187
|
-
// 同时返回 count 和原列表,使模型既能快速判空又不丢失 Extension 细节。
|
|
188
|
-
async execute(_toolCallId, _input, signal) {
|
|
189
|
-
signal?.throwIfAborted();
|
|
190
|
-
return result({
|
|
191
|
-
count: extensions.length,
|
|
192
|
-
extensions,
|
|
193
|
-
});
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
return {
|
|
197
|
-
owner: "core:extensions",
|
|
198
|
-
requiredExecutionLevel: "low",
|
|
199
|
-
retry: "idempotent",
|
|
200
|
-
tool,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// #endregion
|
|
205
151
|
|
|
206
|
-
// #region Code Mode
|
|
207
152
|
|
|
208
153
|
const executeParameters = Type.Object({
|
|
209
154
|
code: Type.String({
|
|
@@ -213,6 +158,7 @@ const executeParameters = Type.Object({
|
|
|
213
158
|
"Plain JavaScript async function. TypeScript annotations are not supported.",
|
|
214
159
|
}),
|
|
215
160
|
}, { additionalProperties: false });
|
|
161
|
+
|
|
216
162
|
const CODEMODE_EXECUTE_TIMEOUT_MS = 60_000;
|
|
217
163
|
|
|
218
164
|
// 把模型提供的代码交给 Codemode Runtime 执行,并在外层再压一道截止。
|
|
@@ -331,5 +277,3 @@ export function browserExecutionPiToolCandidate(
|
|
|
331
277
|
tool,
|
|
332
278
|
};
|
|
333
279
|
}
|
|
334
|
-
|
|
335
|
-
// #endregion
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { ConnectorTools } from "@cloudflare/codemode";
|
|
2
|
+
import { STATE_TYPES } from "@cloudflare/shell";
|
|
3
|
+
import type { StateBackend } from "@cloudflare/shell/workers";
|
|
4
|
+
import { StateConnector } from "@cloudflare/shell/workers";
|
|
5
|
+
import type { WorkspacePort } from "../../config/ports";
|
|
6
|
+
|
|
7
|
+
export const WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES = 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
const READ_FILE_PAGE_TYPES = `
|
|
10
|
+
type WorkspaceFilePage =
|
|
11
|
+
| { kind: "text"; size: number; nextOffset: number | null; content: string }
|
|
12
|
+
| { kind: "csv"; size: number; nextOffset: number | null; columns: string[] | null; rows: string[][] };
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const WORKSPACE_STATE_TYPES = `${READ_FILE_PAGE_TYPES}\n${STATE_TYPES.replace(
|
|
16
|
+
/\n\};$/u,
|
|
17
|
+
'\n /** CSV pages have columns and rows; never data. Omit offset first, then pass nextOffset until null. */\n readFilePage(args: { path: string; offset?: number }): Promise<WorkspaceFilePage | null>;\n};',
|
|
18
|
+
)}`;
|
|
19
|
+
|
|
20
|
+
export const WORKSPACE_PAGING_INSTRUCTIONS = `For CSV files, do not make a separate probe read or return page contents. Inspect columns and aggregate rows in the same execution with this loop shape:
|
|
21
|
+
|
|
22
|
+
let page = await state.readFilePage({ path });
|
|
23
|
+
if (!page || page.kind !== "csv") throw new Error("CSV not found");
|
|
24
|
+
const columns = page.columns;
|
|
25
|
+
for (;;) {
|
|
26
|
+
// Aggregate page.rows here. Do not return raw rows.
|
|
27
|
+
if (page.nextOffset === null) break;
|
|
28
|
+
page = await state.readFilePage({ path, offset: page.nextOffset });
|
|
29
|
+
}
|
|
30
|
+
return summary;
|
|
31
|
+
|
|
32
|
+
CSV pages use page.columns and page.rows; never page.data or page.content. Only the first page has columns. Do not use console.log to inspect a page; explicitly return the final summary.`;
|
|
33
|
+
|
|
34
|
+
export class WorkspaceStateConnector extends StateConnector {
|
|
35
|
+
constructor(
|
|
36
|
+
ctx: DurableObjectState | ExecutionContext,
|
|
37
|
+
backend: StateBackend,
|
|
38
|
+
private readonly workspace: WorkspacePort,
|
|
39
|
+
) {
|
|
40
|
+
super(ctx, backend);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected override instructions(): string {
|
|
44
|
+
const base = super.instructions();
|
|
45
|
+
return this.workspace.readFilePage ? `${base}
|
|
46
|
+
|
|
47
|
+
${WORKSPACE_PAGING_INSTRUCTIONS}` : base;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected override tools(): ConnectorTools {
|
|
51
|
+
const tools = super.tools();
|
|
52
|
+
const readFile = tools.readFile!;
|
|
53
|
+
const mkdir = tools.mkdir!;
|
|
54
|
+
const readFilePage = this.workspace.readFilePage;
|
|
55
|
+
return {
|
|
56
|
+
...tools,
|
|
57
|
+
mkdir: {
|
|
58
|
+
...mkdir,
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
path: { type: "string", minLength: 1 },
|
|
63
|
+
options: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: { recursive: { type: "boolean" } },
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ["path"],
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
},
|
|
72
|
+
execute: (args, context) => mkdir.execute(mkdirInput(args), context),
|
|
73
|
+
},
|
|
74
|
+
...(readFilePage
|
|
75
|
+
? {
|
|
76
|
+
readFile: {
|
|
77
|
+
...readFile,
|
|
78
|
+
description:
|
|
79
|
+
`${readFile.description} CSV and files over ${WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES} bytes must use readFilePage.`,
|
|
80
|
+
execute: async (args, context) => {
|
|
81
|
+
const path = requiredPath(args, "readFile");
|
|
82
|
+
const file = await this.workspace.stat(path);
|
|
83
|
+
if (file?.type === "file" && (
|
|
84
|
+
file.mimeType === "text/csv" ||
|
|
85
|
+
file.size > WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES
|
|
86
|
+
)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${path} requires state.readFilePage({ path: ${JSON.stringify(path)} }); continue with nextOffset until it is null`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return readFile.execute(args, context);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
readFilePage: {
|
|
95
|
+
description:
|
|
96
|
+
"Read up to 512 KiB or 2,000 CSV rows from a Workspace file. " +
|
|
97
|
+
"A CSV page returns columns and rows and has no data field; rows are string arrays aligned to columns. " +
|
|
98
|
+
"A text page returns content. Omit offset on the first call, then pass page.nextOffset until it is null. " +
|
|
99
|
+
"Prefer this over readFile for CSV and large files.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
path: { type: "string", minLength: 1, description: "Absolute Workspace file path." },
|
|
104
|
+
offset: {
|
|
105
|
+
type: "integer",
|
|
106
|
+
minimum: 0,
|
|
107
|
+
description: "Previous page.nextOffset. Omit on the first call.",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
required: ["path"],
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
},
|
|
113
|
+
replay: "reexecute",
|
|
114
|
+
execute: (args: unknown) => {
|
|
115
|
+
const input = readFilePageInput(args);
|
|
116
|
+
return readFilePage.call(this.workspace, input.path, input.offset);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
: {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override async getTypeScriptTypes(): Promise<string> {
|
|
125
|
+
return this.workspace.readFilePage ? WORKSPACE_STATE_TYPES : STATE_TYPES;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function mkdirInput(value: unknown): {
|
|
130
|
+
path: string;
|
|
131
|
+
options?: { recursive?: boolean };
|
|
132
|
+
} {
|
|
133
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
134
|
+
throw new Error("state.mkdir requires an object argument");
|
|
135
|
+
}
|
|
136
|
+
const input = value as Record<string, unknown>;
|
|
137
|
+
if (Object.keys(input).some((key) => !["path", "options"].includes(key))) {
|
|
138
|
+
throw new Error("state.mkdir accepts only path and options");
|
|
139
|
+
}
|
|
140
|
+
if (typeof input.path !== "string" || input.path.length === 0) {
|
|
141
|
+
throw new Error("state.mkdir requires path");
|
|
142
|
+
}
|
|
143
|
+
if (input.options === undefined) return { path: input.path };
|
|
144
|
+
if (!input.options || typeof input.options !== "object" || Array.isArray(input.options)) {
|
|
145
|
+
throw new Error("state.mkdir options must be an object");
|
|
146
|
+
}
|
|
147
|
+
const options = input.options as Record<string, unknown>;
|
|
148
|
+
if (Object.keys(options).some((key) => key !== "recursive")) {
|
|
149
|
+
throw new Error("state.mkdir options accepts only recursive");
|
|
150
|
+
}
|
|
151
|
+
if (options.recursive !== undefined && typeof options.recursive !== "boolean") {
|
|
152
|
+
throw new Error("state.mkdir options.recursive must be boolean");
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
path: input.path,
|
|
156
|
+
options: options.recursive === undefined
|
|
157
|
+
? {}
|
|
158
|
+
: { recursive: options.recursive },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function requiredPath(value: unknown, method: string): string {
|
|
163
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
164
|
+
typeof (value as { path?: unknown }).path !== "string" ||
|
|
165
|
+
!(value as { path: string }).path) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`state.${method} takes one object argument: state.${method}({ path: "/path/to/file" })`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return (value as { path: string }).path;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readFilePageInput(value: unknown): {
|
|
174
|
+
path: string;
|
|
175
|
+
offset?: number;
|
|
176
|
+
} {
|
|
177
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
178
|
+
throw new Error("state.readFilePage requires an object argument");
|
|
179
|
+
}
|
|
180
|
+
const input = value as Record<string, unknown>;
|
|
181
|
+
const keys = Object.keys(input);
|
|
182
|
+
if (keys.some((key) => !["path", "offset"].includes(key))) {
|
|
183
|
+
throw new Error("state.readFilePage accepts only path and offset");
|
|
184
|
+
}
|
|
185
|
+
if (typeof input.path !== "string" || input.path.length === 0) {
|
|
186
|
+
throw new Error("state.readFilePage requires path");
|
|
187
|
+
}
|
|
188
|
+
const offset = input.offset;
|
|
189
|
+
if (offset !== undefined && (!Number.isInteger(offset) || (offset as number) < 0)) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"state.readFilePage offset must be a previous page.nextOffset; omit it on the first call",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
path: input.path,
|
|
196
|
+
...(offset === undefined ? {} : { offset: offset as number }),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { PiLoadedExtension } from "../../context/extension/assembly";
|
|
4
|
+
import type { PiToolCandidate } from "../runtime/contracts";
|
|
5
|
+
import { result } from "../runtime/result";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const noParameters = Type.Object({}, { additionalProperties: false });
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 为当前已加载 Extension 列表创建一个 Pi 工具候选项。
|
|
12
|
+
*
|
|
13
|
+
* Runtime 组装层在 Extension 装载完成后调用,模型需要发现当前可用扩展时使用该工具。
|
|
14
|
+
*
|
|
15
|
+
* 候选项捕获准备阶段的只读列表,这与 pinned Runtime 组装保持一致,不能在执行时重新扫描出另一份 Extension 集合。
|
|
16
|
+
*/
|
|
17
|
+
export function listExtensionsPiToolCandidate(
|
|
18
|
+
extensions: readonly PiLoadedExtension[],
|
|
19
|
+
): PiToolCandidate {
|
|
20
|
+
const tool: AgentTool<typeof noParameters> = {
|
|
21
|
+
name: "list_extensions",
|
|
22
|
+
label: "List extensions",
|
|
23
|
+
description: "List all currently loaded extensions and their tools.",
|
|
24
|
+
parameters: noParameters,
|
|
25
|
+
// 返回这个 pinned Runtime 已加载的 Extension 快照。
|
|
26
|
+
// Pi 工具循环在模型选择 `list_extensions` 时调用,调用前允许 Turn 取消。
|
|
27
|
+
// 同时返回 count 和原列表,使模型既能快速判空又不丢失 Extension 细节。
|
|
28
|
+
async execute(_toolCallId, _input, signal) {
|
|
29
|
+
signal?.throwIfAborted();
|
|
30
|
+
return result({
|
|
31
|
+
count: extensions.length,
|
|
32
|
+
extensions,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
owner: "core:extensions",
|
|
38
|
+
requiredExecutionLevel: "low",
|
|
39
|
+
retry: "idempotent",
|
|
40
|
+
tool,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
export * from "./ai-
|
|
2
|
-
export * from "./approval";
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
1
|
+
export * from "./adapters/ai-to-pi";
|
|
2
|
+
export * from "./runtime/approval";
|
|
3
|
+
export * from "./assistant";
|
|
4
|
+
export * from "./runtime/contracts";
|
|
5
|
+
export * from "./runtime/compiler";
|
|
6
|
+
export * from "./codemode";
|
|
7
|
+
export * from "./codemode/adapter";
|
|
8
|
+
export * from "./extension";
|
|
9
|
+
export * from "./adapters/declared";
|
|
10
|
+
export * from "./runtime/decision-gate";
|
|
11
|
+
export * from "./runtime/human-decisions";
|
|
12
|
+
export * from "./mcp/gateway";
|
|
11
13
|
export * from "./mcp";
|
|
12
|
-
export * from "./interaction";
|
|
14
|
+
export * from "./runtime/interaction";
|
|
13
15
|
export * from "./schedule";
|
|
14
16
|
export * from "./skill";
|
|
15
17
|
export * from "./subagent";
|
|
16
|
-
export * from "./subagent
|
|
17
|
-
export * from "./subagent
|
|
18
|
-
export * from "./subagent
|
|
19
|
-
export * from "./subagent
|
|
20
|
-
export * from "./subagent
|
|
21
|
-
export * from "./temporary
|
|
22
|
-
export * from "./temporary
|
|
23
|
-
export * from "./temporary
|
|
24
|
-
export * from "./temporary
|
|
25
|
-
export * from "./temporary
|
|
26
|
-
export * from "./
|
|
27
|
-
export * from "./
|
|
28
|
-
export * from "./workspace
|
|
29
|
-
export * from "./workspace
|
|
30
|
-
export * from "./web
|
|
18
|
+
export * from "./subagent/authority";
|
|
19
|
+
export * from "./subagent/repository";
|
|
20
|
+
export * from "./subagent/runtime";
|
|
21
|
+
export * from "./subagent/types/contract";
|
|
22
|
+
export * from "./subagent/types/registry";
|
|
23
|
+
export * from "./subagent/temporary/approvals";
|
|
24
|
+
export * from "./subagent/temporary/core";
|
|
25
|
+
export * from "./subagent/temporary/coordinator";
|
|
26
|
+
export * from "./subagent/temporary/runner";
|
|
27
|
+
export * from "./subagent/temporary/workspace";
|
|
28
|
+
export * from "./workspace";
|
|
29
|
+
export * from "./sandbox";
|
|
30
|
+
export * from "./workspace/revision";
|
|
31
|
+
export * from "./workspace/versioning";
|
|
32
|
+
export * from "./web/search";
|