@springbrand/agent-runtime 0.2.0-alpha.65 → 0.2.0-alpha.67
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/workspace/scoped-workspace.ts +11 -0
- package/src/index.ts +1 -0
- package/src/kernel/bindings.ts +21 -0
- package/src/lib/prompt.ts +5 -3
- package/src/pi/tool/core-host.ts +12 -2
- package/src/pi/tool/skill.ts +25 -3
- package/src/pi/tool/workspace-state-connector.ts +143 -0
- package/src/runtime-assembler.ts +14 -0
- package/src/runtime-definition.ts +7 -0
package/package.json
CHANGED
|
@@ -197,6 +197,14 @@ export class ScopedWorkspace implements WorkspacePort, WorkspaceAdminPort {
|
|
|
197
197
|
return this.parent.readFileBytes(await this.guardedPhysical(path));
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
async readFilePage(
|
|
201
|
+
path: string,
|
|
202
|
+
offset?: number,
|
|
203
|
+
) {
|
|
204
|
+
if (!this.parent.readFilePage) return null;
|
|
205
|
+
return this.parent.readFilePage(await this.guardedPhysical(path), offset);
|
|
206
|
+
}
|
|
207
|
+
|
|
200
208
|
async writeFile(path: string, content: string, mimeType?: string) {
|
|
201
209
|
return this.parent.writeFile(
|
|
202
210
|
await this.guardedPhysical(path, true),
|
|
@@ -373,6 +381,9 @@ export function createWorkspacePortFacade(
|
|
|
373
381
|
const facade: WorkspacePort = {
|
|
374
382
|
readFile: (path) => workspace.readFile(path),
|
|
375
383
|
readFileBytes: (path) => workspace.readFileBytes(path),
|
|
384
|
+
...(workspace.readFilePage
|
|
385
|
+
? { readFilePage: (path, offset) => workspace.readFilePage!(path, offset) }
|
|
386
|
+
: {}),
|
|
376
387
|
writeFile: (path, content, mimeType) =>
|
|
377
388
|
workspace.writeFile(path, content, mimeType),
|
|
378
389
|
writeFileBytes: (path, data, mimeType) =>
|
package/src/index.ts
CHANGED
package/src/kernel/bindings.ts
CHANGED
|
@@ -59,6 +59,19 @@ export type WorkspaceFileInspection = WorkspaceFileInfo & {
|
|
|
59
59
|
evidence?: WorkspaceFileEvidence;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
type WorkspaceFilePageBase = {
|
|
63
|
+
size: number;
|
|
64
|
+
nextOffset: number | null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type WorkspaceFilePage =
|
|
68
|
+
| (WorkspaceFilePageBase & { kind: "text"; content: string })
|
|
69
|
+
| (WorkspaceFilePageBase & {
|
|
70
|
+
kind: "csv";
|
|
71
|
+
columns: string[] | null;
|
|
72
|
+
rows: string[][];
|
|
73
|
+
});
|
|
74
|
+
|
|
62
75
|
/**
|
|
63
76
|
* 向 Runtime 提供已限定作用域的文件操作。
|
|
64
77
|
*
|
|
@@ -86,6 +99,14 @@ export interface WorkspacePort {
|
|
|
86
99
|
* 它与 `readFile` 分开,避免 Runtime 猜测字符编码。
|
|
87
100
|
*/
|
|
88
101
|
readFileBytes(path: string): Promise<Uint8Array | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Reads one bounded page without buffering the complete file.
|
|
104
|
+
* CSV pages preserve complete records and return parsed columns and rows.
|
|
105
|
+
*/
|
|
106
|
+
readFilePage?(
|
|
107
|
+
path: string,
|
|
108
|
+
offset?: number,
|
|
109
|
+
): Promise<WorkspaceFilePage | null>;
|
|
89
110
|
/**
|
|
90
111
|
* 覆盖写入一个文本文件。
|
|
91
112
|
*
|
package/src/lib/prompt.ts
CHANGED
|
@@ -99,11 +99,13 @@ export const BROWSER =
|
|
|
99
99
|
|
|
100
100
|
// Uploaded-file routing prevents lossy markdown conversions from becoming data sources.
|
|
101
101
|
export const FILES =
|
|
102
|
-
"Uploaded files live under /
|
|
102
|
+
"Uploaded files live under /inputs/ in your workspace; convertible formats have a companion " +
|
|
103
103
|
"'<file>.md' (structured markdown). Policy: to summarize, quote, or search files, use state.* inside execute. " +
|
|
104
|
+
"For every CSV analysis in Code Mode, start with state.readFilePage({ path }); CSV pages expose columns and rows, never data. Rows are arrays aligned to columns, not objects. Never call state.readFile for a CSV. " +
|
|
104
105
|
"To compute/aggregate/transform (especially csv/xlsx), write code in execute Code Mode or the Linux Sandbox that reads " +
|
|
105
|
-
"the ORIGINAL file — converted markdown tables are not for computation
|
|
106
|
-
"
|
|
106
|
+
"the ORIGINAL file — converted markdown tables are not for computation. " +
|
|
107
|
+
"For other large text files in Code Mode, use state.readFilePage({ path }) and continue until nextOffset is null. " +
|
|
108
|
+
"Large spreadsheets may have no .md at all. For formats without a companion .md (e.g. pptx: unzip and read ppt/slides/*.xml; " +
|
|
107
109
|
"zip archives; unknown types), parse the original in the sandbox with JS.";
|
|
108
110
|
|
|
109
111
|
// User-interaction actions are called only when their UI semantics are useful.
|
package/src/pi/tool/core-host.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
RuntimeCodeExecutionPort,
|
|
19
19
|
WorkspacePort,
|
|
20
20
|
} from "../../kernel/bindings";
|
|
21
|
+
import { WorkspaceStateConnector } from "./workspace-state-connector";
|
|
21
22
|
import type { RuntimeCodeExecutionFactory } from "../../kernel/bindings";
|
|
22
23
|
import { serializeOutput } from "../../lib/artifacts";
|
|
23
24
|
import type { PiToolCandidate } from "./compiler";
|
|
@@ -88,7 +89,7 @@ state.writeFileBytes is not available. Use state.writeFile or state.appendFile f
|
|
|
88
89
|
|
|
89
90
|
Bundled Skill resources are not Workspace files. Read text with tools.read_skill_resource, run bundled scripts with tools.run_skill_script, and copy templates or assets with tools.materialize_skill_resource.
|
|
90
91
|
|
|
91
|
-
User Attachments are already Workspace files below /inputs. Inspect their exact path
|
|
92
|
+
User Attachments are already Workspace files below /inputs. Inspect their exact path before using them. Never raw-fetch an attachment already present below /inputs or materialize its cdnUrl; read it with state.*.
|
|
92
93
|
|
|
93
94
|
When an asset already exists in Workspace, use state.cp to copy it into the current Website directory. Use actual task paths and reference the destination with a relative URL.
|
|
94
95
|
|
|
@@ -336,6 +337,15 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
336
337
|
options.workspace,
|
|
337
338
|
options.workspaceAccessMode,
|
|
338
339
|
);
|
|
340
|
+
const workspaceState = state && options.workspace.readFilePage
|
|
341
|
+
? new WorkspaceStateConnector(
|
|
342
|
+
options.ctx,
|
|
343
|
+
state,
|
|
344
|
+
options.workspace as WorkspacePort & {
|
|
345
|
+
readFilePage: NonNullable<WorkspacePort["readFilePage"]>;
|
|
346
|
+
},
|
|
347
|
+
)
|
|
348
|
+
: undefined;
|
|
339
349
|
const createPort = (innerResults: AgentToolResult<unknown>[]) => {
|
|
340
350
|
let nestedCallOrdinal = 0;
|
|
341
351
|
const { tool } = createExecuteRuntime({
|
|
@@ -348,7 +358,7 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
348
358
|
onResult: (value) => innerResults.push(value),
|
|
349
359
|
}),
|
|
350
360
|
timeout: CODEMODE_SANDBOX_TIMEOUT_MS,
|
|
351
|
-
...(state ? { state } : {}),
|
|
361
|
+
...(workspaceState ? { connectors: [workspaceState] } : state ? { state } : {}),
|
|
352
362
|
name: "execute",
|
|
353
363
|
});
|
|
354
364
|
return toCodeExecutionPort(
|
package/src/pi/tool/skill.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
RuntimeSkillScriptPolicy,
|
|
14
14
|
WorkspacePort,
|
|
15
15
|
} from "../../kernel/bindings";
|
|
16
|
+
import type { RuntimeSkillBodyTransform } from "../../runtime-definition";
|
|
16
17
|
import { STORAGE_LEAF_MAX_CHARS } from "../../layers/context/budget/gate";
|
|
17
18
|
import { serializeOutput } from "../../lib/artifacts";
|
|
18
19
|
import { aiToolToPi } from "./ai-adapter";
|
|
@@ -33,6 +34,7 @@ export interface SkillPiToolOptions {
|
|
|
33
34
|
readonly loader?: WorkerLoader;
|
|
34
35
|
readonly workspace?: WorkspacePort;
|
|
35
36
|
readonly tools?: readonly PiToolCandidate[];
|
|
37
|
+
readonly transformSkillBody?: RuntimeSkillBodyTransform;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -116,7 +118,11 @@ function budgetSkillResources(skill: LoadedSkill): LoadedSkill {
|
|
|
116
118
|
return { ...skill, resources: kept, body: `${skill.body}\n\n${lines.join("\n")}` };
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
function catalogSkillSource(
|
|
121
|
+
function catalogSkillSource(
|
|
122
|
+
binding: PiSkillBinding,
|
|
123
|
+
transformSkillBody: RuntimeSkillBodyTransform | undefined,
|
|
124
|
+
availableToolNames: readonly string[],
|
|
125
|
+
): SkillSource {
|
|
120
126
|
const source = binding.source;
|
|
121
127
|
return {
|
|
122
128
|
id: source.id,
|
|
@@ -132,7 +138,17 @@ function catalogSkillSource(binding: PiSkillBinding): SkillSource {
|
|
|
132
138
|
load: async (name) => {
|
|
133
139
|
const skill = await source.load(name);
|
|
134
140
|
if (!skill) return null;
|
|
135
|
-
const
|
|
141
|
+
const prepared = transformSkillBody
|
|
142
|
+
? {
|
|
143
|
+
...skill,
|
|
144
|
+
body: transformSkillBody({
|
|
145
|
+
name: skill.name,
|
|
146
|
+
body: skill.body,
|
|
147
|
+
availableToolNames,
|
|
148
|
+
}),
|
|
149
|
+
}
|
|
150
|
+
: skill;
|
|
151
|
+
const loaded = budgetSkillResources(prepared);
|
|
136
152
|
const hasOversizedResource = skill.resources?.some((resource) =>
|
|
137
153
|
typeof resource.size === "number" &&
|
|
138
154
|
resource.size > SKILL_RESOURCE_BUDGET_BYTES
|
|
@@ -420,8 +436,14 @@ export async function skillPiToolCandidates(
|
|
|
420
436
|
): Promise<PiToolCandidate[]> {
|
|
421
437
|
if (bindings.length === 0) return [];
|
|
422
438
|
|
|
439
|
+
const availableToolNames = (options.tools ?? []).map(({ tool }) => tool.name);
|
|
440
|
+
|
|
423
441
|
const registry = new SkillRegistry(
|
|
424
|
-
bindings.map(catalogSkillSource
|
|
442
|
+
bindings.map((binding) => catalogSkillSource(
|
|
443
|
+
binding,
|
|
444
|
+
options.transformSkillBody,
|
|
445
|
+
availableToolNames,
|
|
446
|
+
)),
|
|
425
447
|
scriptRunner(bindings, options),
|
|
426
448
|
);
|
|
427
449
|
await registry.load();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { StateBackend } from "@cloudflare/shell/workers";
|
|
2
|
+
import { StateConnector } from "@cloudflare/shell/workers";
|
|
3
|
+
import type { ConnectorTools } from "@cloudflare/codemode";
|
|
4
|
+
import type { WorkspacePort } from "../../kernel/bindings";
|
|
5
|
+
|
|
6
|
+
export const WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES = 1024 * 1024;
|
|
7
|
+
|
|
8
|
+
const READ_FILE_PAGE_TYPES = `
|
|
9
|
+
type WorkspaceFilePage =
|
|
10
|
+
| { kind: "text"; size: number; nextOffset: number | null; content: string }
|
|
11
|
+
| { kind: "csv"; size: number; nextOffset: number | null; columns: string[] | null; rows: string[][] };
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
type PagedWorkspacePort = WorkspacePort & {
|
|
15
|
+
readFilePage: NonNullable<WorkspacePort["readFilePage"]>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class WorkspaceStateConnector extends StateConnector {
|
|
19
|
+
constructor(
|
|
20
|
+
ctx: DurableObjectState | ExecutionContext,
|
|
21
|
+
backend: StateBackend,
|
|
22
|
+
private readonly workspace: PagedWorkspacePort,
|
|
23
|
+
) {
|
|
24
|
+
super(ctx, backend);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected override instructions(): string {
|
|
28
|
+
const base = super.instructions();
|
|
29
|
+
return `${base}
|
|
30
|
+
|
|
31
|
+
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:
|
|
32
|
+
|
|
33
|
+
let page = await state.readFilePage({ path });
|
|
34
|
+
if (!page || page.kind !== "csv") throw new Error("CSV not found");
|
|
35
|
+
const columns = page.columns;
|
|
36
|
+
for (;;) {
|
|
37
|
+
// Aggregate page.rows here. Do not return raw rows.
|
|
38
|
+
if (page.nextOffset === null) break;
|
|
39
|
+
page = await state.readFilePage({ path, offset: page.nextOffset });
|
|
40
|
+
}
|
|
41
|
+
return summary;
|
|
42
|
+
|
|
43
|
+
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.`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected override tools(): ConnectorTools {
|
|
47
|
+
const tools = super.tools();
|
|
48
|
+
const readFile = tools.readFile!;
|
|
49
|
+
return {
|
|
50
|
+
...tools,
|
|
51
|
+
readFile: {
|
|
52
|
+
...readFile,
|
|
53
|
+
description:
|
|
54
|
+
`Read a small non-CSV file as text. CSV and files over ${WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES} bytes must use readFilePage.`,
|
|
55
|
+
execute: async (args, context) => {
|
|
56
|
+
const path = requiredPath(args, "readFile");
|
|
57
|
+
const file = await this.workspace.stat(path);
|
|
58
|
+
if (file?.type === "file" && (
|
|
59
|
+
file.mimeType === "text/csv" ||
|
|
60
|
+
file.size > WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES
|
|
61
|
+
)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${path} requires state.readFilePage({ path: ${JSON.stringify(path)} }); continue with nextOffset until it is null`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return readFile.execute(args, context);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
readFilePage: {
|
|
70
|
+
description:
|
|
71
|
+
"Read up to 512 KiB or 2,000 CSV rows from a Workspace file. " +
|
|
72
|
+
"A CSV page returns columns and rows and has no data field; rows are string arrays aligned to columns. " +
|
|
73
|
+
"A text page returns content. Omit offset on the first call, then pass page.nextOffset until it is null. " +
|
|
74
|
+
"Prefer this over readFile for CSV and large files.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
path: { type: "string", minLength: 1, description: "Absolute Workspace file path." },
|
|
79
|
+
offset: {
|
|
80
|
+
type: "integer",
|
|
81
|
+
minimum: 0,
|
|
82
|
+
description: "Previous page.nextOffset. Omit on the first call.",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
required: ["path"],
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
},
|
|
88
|
+
replay: "reexecute",
|
|
89
|
+
execute: (args) => {
|
|
90
|
+
const input = readFilePageInput(args);
|
|
91
|
+
return this.workspace.readFilePage(input.path, input.offset);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override async getTypeScriptTypes(): Promise<string> {
|
|
98
|
+
const types = await super.getTypeScriptTypes();
|
|
99
|
+
const addition = [
|
|
100
|
+
" /** CSV pages have columns and rows; never data. Omit offset first, then pass nextOffset until null. */",
|
|
101
|
+
" readFilePage(args: { path: string; offset?: number }): Promise<WorkspaceFilePage | null>;",
|
|
102
|
+
].join("\n");
|
|
103
|
+
return `${READ_FILE_PAGE_TYPES}\n${types.replace(/\n\};$/u, `\n${addition}\n};`)}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function requiredPath(value: unknown, method: string): string {
|
|
108
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
109
|
+
typeof (value as { path?: unknown }).path !== "string" ||
|
|
110
|
+
!(value as { path: string }).path) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`state.${method} takes one object argument: state.${method}({ path: "/path/to/file" })`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return (value as { path: string }).path;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readFilePageInput(value: unknown): {
|
|
119
|
+
path: string;
|
|
120
|
+
offset?: number;
|
|
121
|
+
} {
|
|
122
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
123
|
+
throw new Error("state.readFilePage requires an object argument");
|
|
124
|
+
}
|
|
125
|
+
const input = value as Record<string, unknown>;
|
|
126
|
+
const keys = Object.keys(input);
|
|
127
|
+
if (keys.some((key) => !["path", "offset"].includes(key))) {
|
|
128
|
+
throw new Error("state.readFilePage accepts only path and offset");
|
|
129
|
+
}
|
|
130
|
+
if (typeof input.path !== "string" || input.path.length === 0) {
|
|
131
|
+
throw new Error("state.readFilePage requires path");
|
|
132
|
+
}
|
|
133
|
+
const offset = input.offset;
|
|
134
|
+
if (offset !== undefined && (!Number.isInteger(offset) || (offset as number) < 0)) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"state.readFilePage offset must be a previous page.nextOffset; omit it on the first call",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
path: input.path,
|
|
141
|
+
...(offset === undefined ? {} : { offset: offset as number }),
|
|
142
|
+
};
|
|
143
|
+
}
|
package/src/runtime-assembler.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
RuntimeSettings,
|
|
45
45
|
RuntimeExtensionContribution,
|
|
46
46
|
RuntimeProfileContribution,
|
|
47
|
+
RuntimeSkillBodyTransform,
|
|
47
48
|
RuntimeSkillContribution,
|
|
48
49
|
RuntimeToolSurfacePolicy,
|
|
49
50
|
} from "./runtime-definition";
|
|
@@ -90,6 +91,7 @@ export interface RuntimeAssemblyInput {
|
|
|
90
91
|
readonly sandbox?: RuntimeSandboxPort;
|
|
91
92
|
readonly memory?: RuntimeMemoryPort;
|
|
92
93
|
readonly hostTools: readonly PiToolCandidate[];
|
|
94
|
+
readonly transformSkillBody?: RuntimeSkillBodyTransform;
|
|
93
95
|
readonly skills: readonly RuntimeSkillContribution[];
|
|
94
96
|
readonly connectors: readonly RuntimeMcpServer[];
|
|
95
97
|
readonly gateway?: RuntimeGatewayPort;
|
|
@@ -168,6 +170,7 @@ interface ToolSurfaceInput {
|
|
|
168
170
|
readonly profile: RuntimeMemoryProfile;
|
|
169
171
|
};
|
|
170
172
|
readonly hostTools: readonly PiToolCandidate[];
|
|
173
|
+
readonly transformSkillBody?: RuntimeSkillBodyTransform;
|
|
171
174
|
readonly skills: readonly RuntimeSkillSourceBinding[];
|
|
172
175
|
readonly enabledSubagents: readonly string[];
|
|
173
176
|
readonly webSearch?: Parameters<typeof basePiToolCandidates>[0];
|
|
@@ -236,6 +239,9 @@ async function createToolSurface(
|
|
|
236
239
|
loader: input.platform.loader,
|
|
237
240
|
...(input.workspace ? { workspace: input.workspace } : {}),
|
|
238
241
|
tools: scriptCandidates,
|
|
242
|
+
...(input.transformSkillBody
|
|
243
|
+
? { transformSkillBody: input.transformSkillBody }
|
|
244
|
+
: {}),
|
|
239
245
|
})),
|
|
240
246
|
...baseCandidates,
|
|
241
247
|
...memoryCandidates,
|
|
@@ -369,6 +375,7 @@ class RuntimeBuilder {
|
|
|
369
375
|
private memoryProfile: RuntimeMemoryProfile = DISABLED_MEMORY;
|
|
370
376
|
private memory?: RuntimeMemoryPort;
|
|
371
377
|
private readonly hostTools: PiToolCandidate[] = [];
|
|
378
|
+
private transformSkillBody?: RuntimeSkillBodyTransform;
|
|
372
379
|
private toolPolicy?: RuntimeToolSurfacePolicy;
|
|
373
380
|
private readonly skillSources = new Map<
|
|
374
381
|
string,
|
|
@@ -404,6 +411,7 @@ class RuntimeBuilder {
|
|
|
404
411
|
this.memoryProfile = input.memoryProfile;
|
|
405
412
|
this.memory = input.memory;
|
|
406
413
|
this.hostTools.push(...input.hostTools);
|
|
414
|
+
this.transformSkillBody = input.transformSkillBody;
|
|
407
415
|
this.toolPolicy = input.toolPolicy;
|
|
408
416
|
for (const skill of input.skills) this.addSkill(skill);
|
|
409
417
|
for (const connector of input.connectors) this.addConnector(connector);
|
|
@@ -619,6 +627,9 @@ class RuntimeBuilder {
|
|
|
619
627
|
[...this.enabledSubagents],
|
|
620
628
|
),
|
|
621
629
|
],
|
|
630
|
+
...(this.transformSkillBody
|
|
631
|
+
? { transformSkillBody: this.transformSkillBody }
|
|
632
|
+
: {}),
|
|
622
633
|
skills: skillSources,
|
|
623
634
|
enabledSubagents: [...this.enabledSubagents],
|
|
624
635
|
...(webSearch ? { webSearch } : {}),
|
|
@@ -786,6 +797,9 @@ export async function assembleRuntimeSnapshot<
|
|
|
786
797
|
? { subagents: toolAssembly.bindings.subagents }
|
|
787
798
|
: {}),
|
|
788
799
|
hostTools,
|
|
800
|
+
...(resources.transformSkillBody
|
|
801
|
+
? { transformSkillBody: resources.transformSkillBody }
|
|
802
|
+
: {}),
|
|
789
803
|
skills:
|
|
790
804
|
input.ctx.role === "primary" && toolAssembly.bindings?.workspace
|
|
791
805
|
? [...BUILT_IN_RUNTIME_SKILLS, ...resources.skills]
|
|
@@ -82,12 +82,19 @@ export interface ResolvedConnectors {
|
|
|
82
82
|
readonly degradations: readonly RuntimeDegradation[];
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export type RuntimeSkillBodyTransform = (input: {
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly body: string;
|
|
88
|
+
readonly availableToolNames: readonly string[];
|
|
89
|
+
}) => string;
|
|
90
|
+
|
|
85
91
|
export interface ResolvedResources {
|
|
86
92
|
readonly settings: RuntimeSettings;
|
|
87
93
|
readonly provider: RuntimeProviderPort;
|
|
88
94
|
readonly platform: RuntimePlatformPort;
|
|
89
95
|
readonly turnEvents?: RuntimeTurnEventsPort;
|
|
90
96
|
readonly admission?: RuntimeSubmissionAdmissionHook;
|
|
97
|
+
readonly transformSkillBody?: RuntimeSkillBodyTransform;
|
|
91
98
|
readonly skills: readonly RuntimeSkillContribution[];
|
|
92
99
|
readonly extensions: readonly RuntimeExtensionContribution[];
|
|
93
100
|
readonly connectors: ResolvedConnectors;
|