@agent-harness-experimental/adapter-pi 0.0.0 → 0.0.1
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/LICENSE +1 -0
- package/README.md +101 -2
- package/dist/adapter.d.ts +18 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +143 -0
- package/dist/adapter.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/model-resolver.d.mts +31 -0
- package/dist/model-resolver.d.mts.map +1 -0
- package/dist/model-resolver.mjs +42 -0
- package/dist/model-resolver.mjs.map +1 -0
- package/dist/model-resolver.mts +76 -0
- package/dist/prepare-step-extension.d.ts +31 -0
- package/dist/prepare-step-extension.d.ts.map +1 -0
- package/dist/prepare-step-extension.js +48 -0
- package/dist/prepare-step-extension.js.map +1 -0
- package/dist/remote-ops.d.ts +40 -0
- package/dist/remote-ops.d.ts.map +1 -0
- package/dist/remote-ops.js +217 -0
- package/dist/remote-ops.js.map +1 -0
- package/dist/setup.d.ts +6 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +56 -0
- package/dist/setup.js.map +1 -0
- package/dist/shared.d.ts +7 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +41 -0
- package/dist/shared.js.map +1 -0
- package/dist/split-runtime-support.d.ts +92 -0
- package/dist/split-runtime-support.d.ts.map +1 -0
- package/dist/split-runtime-support.js +429 -0
- package/dist/split-runtime-support.js.map +1 -0
- package/dist/split-runtime.d.ts +63 -0
- package/dist/split-runtime.d.ts.map +1 -0
- package/dist/split-runtime.js +894 -0
- package/dist/split-runtime.js.map +1 -0
- package/dist/workspace-mirror.d.ts +8 -0
- package/dist/workspace-mirror.d.ts.map +1 -0
- package/dist/workspace-mirror.js +150 -0
- package/dist/workspace-mirror.js.map +1 -0
- package/dist/workspace-vfs.d.ts +8 -0
- package/dist/workspace-vfs.d.ts.map +1 -0
- package/dist/workspace-vfs.js +300 -0
- package/dist/workspace-vfs.js.map +1 -0
- package/package.json +59 -3
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
import { attachSplitSandboxContext, detachSplitSandbox, getSandboxRuntimeLayout, startSplitSandbox, stopSplitSandbox, } from 'agent-harness-experimental';
|
|
2
|
+
import { withVercelSandboxBackend } from '@agent-harness-experimental/sandbox-vercel';
|
|
3
|
+
import { Type } from '@sinclair/typebox';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { mkdir } from 'node:fs/promises';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { createPiModelResolver } from './model-resolver.mjs';
|
|
10
|
+
import { createPrepareStepBridge, createPrepareStepExtension } from './prepare-step-extension.js';
|
|
11
|
+
import { createRemoteOperations, createSplitPathMapper } from './remote-ops.js';
|
|
12
|
+
import { createDelegateToolDescription, createSubagentPrompt, filterAgentToolNames } from './shared.js';
|
|
13
|
+
import { PI_SPLIT_BUILTIN_NAMES, PI_VFS_ROOT, REQUEST_HOST_ACCESS_TOOL_NAME, buildRemotePiMetadata, configureAuth, createReplayProxies, extractAssistantText, getErrorText, getPiTerminalError, loadPiCodingAgent, resolveReplayProxyTargets, serializeToolOutput, parseSplitPiAdapterState, } from './split-runtime-support.js';
|
|
14
|
+
import { syncLocalWorkspaceFromSandbox, writeLocalWorkspaceFile } from './workspace-mirror.js';
|
|
15
|
+
import { PiWorkspaceVfs } from './workspace-vfs.js';
|
|
16
|
+
import { msg as protocolMessage } from '@agent-harness-experimental/protocol';
|
|
17
|
+
export { __setPiCodingAgentModuleLoaderForTests, configureAuth, resolveReplayProxyTargets } from './split-runtime-support.js';
|
|
18
|
+
const piSessionEventSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
type: z.string(),
|
|
21
|
+
assistantMessageEvent: z.object({ type: z.string().optional(), delta: z.string().optional() }).passthrough().optional(),
|
|
22
|
+
toolCallId: z.string().optional(),
|
|
23
|
+
toolName: z.string().optional(),
|
|
24
|
+
input: z.unknown().optional(),
|
|
25
|
+
content: z.unknown().optional(),
|
|
26
|
+
isError: z.boolean().optional(),
|
|
27
|
+
error: z.union([z.string(), z.object({ errorMessage: z.string().optional(), stopReason: z.string().optional() }).passthrough()]).optional(),
|
|
28
|
+
message: z
|
|
29
|
+
.object({
|
|
30
|
+
role: z.string().optional(),
|
|
31
|
+
content: z.unknown().optional(),
|
|
32
|
+
stopReason: z.string().optional(),
|
|
33
|
+
errorMessage: z.string().optional(),
|
|
34
|
+
})
|
|
35
|
+
.passthrough()
|
|
36
|
+
.optional(),
|
|
37
|
+
})
|
|
38
|
+
.passthrough();
|
|
39
|
+
function parseNativeEvent(raw) {
|
|
40
|
+
return piSessionEventSchema.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
export class SplitPiRuntime {
|
|
43
|
+
options;
|
|
44
|
+
splitSandboxImageName;
|
|
45
|
+
ctx = null;
|
|
46
|
+
splitSandbox = null;
|
|
47
|
+
workspaceVfs = new PiWorkspaceVfs();
|
|
48
|
+
session = null;
|
|
49
|
+
resourceLoader = null;
|
|
50
|
+
authStorage = null;
|
|
51
|
+
modelRegistry = null;
|
|
52
|
+
settingsManager = null;
|
|
53
|
+
resolverEnv = process.env;
|
|
54
|
+
localRoot = null;
|
|
55
|
+
localWorkDir = null;
|
|
56
|
+
localAgentDir = null;
|
|
57
|
+
localSessionDir = null;
|
|
58
|
+
logicalRoot = null;
|
|
59
|
+
logicalWorkDir = null;
|
|
60
|
+
logicalAgentDir = null;
|
|
61
|
+
logicalSessionDir = null;
|
|
62
|
+
sessionFile;
|
|
63
|
+
importedState;
|
|
64
|
+
unsubscribeSession = null;
|
|
65
|
+
pendingToolResults = new Map();
|
|
66
|
+
pendingToolDecisions = new Map();
|
|
67
|
+
observedToolNames = new Map();
|
|
68
|
+
currentInstructions;
|
|
69
|
+
turnTerminalError;
|
|
70
|
+
resolveTurnTerminalError;
|
|
71
|
+
resolvePromptPause;
|
|
72
|
+
resolvePromptCompletion;
|
|
73
|
+
promptStarted = false;
|
|
74
|
+
streamedAssistantText = '';
|
|
75
|
+
replayProxies = [];
|
|
76
|
+
prepareStepBridge = createPrepareStepBridge();
|
|
77
|
+
constructor(options, splitSandboxImageName) {
|
|
78
|
+
this.options = options;
|
|
79
|
+
this.splitSandboxImageName = splitSandboxImageName;
|
|
80
|
+
}
|
|
81
|
+
importState(state) {
|
|
82
|
+
this.importedState = parseSplitPiAdapterState(state);
|
|
83
|
+
}
|
|
84
|
+
exportState() {
|
|
85
|
+
if (!this.localRoot && !this.sessionFile) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...(this.localRoot ? { localRoot: this.localRoot } : {}),
|
|
90
|
+
...(this.sessionFile ? { sessionFile: this.sessionFile } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
resolveLocalPaths(sessionId) {
|
|
94
|
+
const root = this.importedState?.localRoot ?? path.join(os.tmpdir(), 'ai-sdk-agent', 'pi', sessionId);
|
|
95
|
+
const safeSessionId = sessionId.replaceAll(/[\\/:\0]/g, '-');
|
|
96
|
+
const logicalRoot = path.join(PI_VFS_ROOT, safeSessionId);
|
|
97
|
+
return {
|
|
98
|
+
localRoot: root,
|
|
99
|
+
localWorkDir: path.join(root, 'workspace'),
|
|
100
|
+
localAgentDir: path.join(root, 'agent'),
|
|
101
|
+
localSessionDir: path.join(root, 'sessions'),
|
|
102
|
+
logicalRoot,
|
|
103
|
+
logicalWorkDir: path.join(logicalRoot, 'workspace'),
|
|
104
|
+
logicalAgentDir: path.join(logicalRoot, 'agent'),
|
|
105
|
+
logicalSessionDir: path.join(logicalRoot, 'sessions'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async ensureLocalMirror(ctx) {
|
|
109
|
+
const paths = this.resolveLocalPaths(ctx.sessionId);
|
|
110
|
+
this.localRoot = paths.localRoot;
|
|
111
|
+
this.localWorkDir = paths.localWorkDir;
|
|
112
|
+
this.localAgentDir = paths.localAgentDir;
|
|
113
|
+
this.localSessionDir = paths.localSessionDir;
|
|
114
|
+
this.logicalRoot = paths.logicalRoot;
|
|
115
|
+
this.logicalWorkDir = paths.logicalWorkDir;
|
|
116
|
+
this.logicalAgentDir = paths.logicalAgentDir;
|
|
117
|
+
this.logicalSessionDir = paths.logicalSessionDir;
|
|
118
|
+
await mkdir(this.localWorkDir, { recursive: true });
|
|
119
|
+
await mkdir(this.localAgentDir, { recursive: true });
|
|
120
|
+
await mkdir(this.localSessionDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
mountWorkspaceVfs() {
|
|
123
|
+
if (!this.localRoot || !this.logicalRoot) {
|
|
124
|
+
throw new Error('Split Pi local workspace was not initialized');
|
|
125
|
+
}
|
|
126
|
+
this.workspaceVfs.mount(this.localRoot, this.logicalRoot);
|
|
127
|
+
}
|
|
128
|
+
async syncLocalWorkspace() {
|
|
129
|
+
if (!this.splitSandbox || !this.localWorkDir) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
await syncLocalWorkspaceFromSandbox({
|
|
133
|
+
sandbox: this.splitSandbox.session,
|
|
134
|
+
remoteWorkDir: this.splitSandbox.workDir,
|
|
135
|
+
localWorkDir: this.localWorkDir,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async waitForToolDecision(requestId, event) {
|
|
139
|
+
return await new Promise((resolve) => {
|
|
140
|
+
this.pendingToolDecisions.set(requestId, resolve);
|
|
141
|
+
this.resolvePromptPause?.();
|
|
142
|
+
this.resolvePromptPause = undefined;
|
|
143
|
+
this.ctx?.emit(protocolMessage.toolCall({ requestId, toolName: event.toolName, toolCallId: event.toolCallId, input: event.input }));
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async waitForToolResult(requestId, event) {
|
|
147
|
+
return await new Promise((resolve) => {
|
|
148
|
+
this.pendingToolResults.set(requestId, resolve);
|
|
149
|
+
this.resolvePromptPause?.();
|
|
150
|
+
this.resolvePromptPause = undefined;
|
|
151
|
+
this.ctx?.emit(protocolMessage.toolCall({ requestId, toolName: event.toolName, toolCallId: event.toolCallId, input: event.input }));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async buildToolDefinitions(ctx, modelRegistry, authStorage, input) {
|
|
155
|
+
if (!this.splitSandbox || !this.localWorkDir || !this.logicalWorkDir) {
|
|
156
|
+
throw new Error('Split Pi runtime is not started');
|
|
157
|
+
}
|
|
158
|
+
const { DefaultResourceLoader, SessionManager, SettingsManager, createAgentSession, defineTool } = await loadPiCodingAgent();
|
|
159
|
+
const runtime = this;
|
|
160
|
+
const includedTools = new Set(input.toolNames ?? [
|
|
161
|
+
...PI_SPLIT_BUILTIN_NAMES,
|
|
162
|
+
...(ctx.requestHostAccess ? [REQUEST_HOST_ACCESS_TOOL_NAME] : []),
|
|
163
|
+
...ctx.registeredTools.map((tool) => tool.name),
|
|
164
|
+
]);
|
|
165
|
+
const paths = createSplitPathMapper(this.logicalWorkDir, this.splitSandbox.workDir);
|
|
166
|
+
const remote = createRemoteOperations({
|
|
167
|
+
sandbox: this.splitSandbox.session,
|
|
168
|
+
paths,
|
|
169
|
+
env: this.splitSandbox.commandEnv,
|
|
170
|
+
onFileChange: (event, relativePath, content) => {
|
|
171
|
+
if (runtime.localWorkDir) {
|
|
172
|
+
void writeLocalWorkspaceFile(runtime.localWorkDir, relativePath, content);
|
|
173
|
+
}
|
|
174
|
+
runtime.ctx?.emit(protocolMessage.fileChange({ event, path: relativePath, content: content.toString('base64') }));
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const definitions = [];
|
|
178
|
+
const addBuiltin = (schema, args) => {
|
|
179
|
+
if (!includedTools.has(args.name)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
definitions.push(defineTool({
|
|
183
|
+
name: args.name,
|
|
184
|
+
label: args.name,
|
|
185
|
+
description: args.description,
|
|
186
|
+
parameters: Type.Unsafe(schema),
|
|
187
|
+
async execute(toolCallId, params, signal) {
|
|
188
|
+
const inputParams = params;
|
|
189
|
+
const requestId = toolCallId;
|
|
190
|
+
const decision = await runtime.waitForToolDecision(requestId, {
|
|
191
|
+
toolName: args.name,
|
|
192
|
+
toolCallId,
|
|
193
|
+
input: inputParams,
|
|
194
|
+
});
|
|
195
|
+
runtime.pendingToolDecisions.delete(requestId);
|
|
196
|
+
if (!decision.approved) {
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: serializeToolOutput({
|
|
202
|
+
type: 'execution-denied',
|
|
203
|
+
reason: decision.reason ?? 'Tool execution denied',
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
details: {},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const output = await args.execute(inputParams, signal);
|
|
212
|
+
runtime.ctx?.emit(protocolMessage.toolResult({ toolName: args.name, toolCallId, output }));
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: 'text', text: serializeToolOutput(output) }],
|
|
215
|
+
details: {},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
const output = { error: getErrorText(error) };
|
|
220
|
+
runtime.ctx?.emit(protocolMessage.toolResult({ toolName: args.name, toolCallId, output }));
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: 'text', text: serializeToolOutput(output) }],
|
|
223
|
+
details: {},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
addBuiltin({
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
file_path: { type: 'string' },
|
|
233
|
+
},
|
|
234
|
+
required: ['file_path'],
|
|
235
|
+
}, {
|
|
236
|
+
name: 'read',
|
|
237
|
+
description: 'Read file contents',
|
|
238
|
+
async execute(params) {
|
|
239
|
+
return (await remote.readBuffer(String(params.file_path ?? ''))).toString('utf8');
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
addBuiltin({
|
|
243
|
+
type: 'object',
|
|
244
|
+
properties: {
|
|
245
|
+
file_path: { type: 'string' },
|
|
246
|
+
content: { type: 'string' },
|
|
247
|
+
},
|
|
248
|
+
required: ['file_path', 'content'],
|
|
249
|
+
}, {
|
|
250
|
+
name: 'write',
|
|
251
|
+
description: 'Write content to a file',
|
|
252
|
+
async execute(params) {
|
|
253
|
+
await remote.writeFile(String(params.file_path ?? ''), String(params.content ?? ''));
|
|
254
|
+
return `Wrote ${paths.toRelativePath(paths.toRemotePath(String(params.file_path ?? '')))}`;
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
addBuiltin({
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
file_path: { type: 'string' },
|
|
261
|
+
old_string: { type: 'string' },
|
|
262
|
+
new_string: { type: 'string' },
|
|
263
|
+
},
|
|
264
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
265
|
+
}, {
|
|
266
|
+
name: 'edit',
|
|
267
|
+
description: 'Edit a file by replacing text',
|
|
268
|
+
async execute(params) {
|
|
269
|
+
await remote.editFile(String(params.file_path ?? ''), String(params.old_string ?? ''), String(params.new_string ?? ''));
|
|
270
|
+
return `Edited ${paths.toRelativePath(paths.toRemotePath(String(params.file_path ?? '')))}`;
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
addBuiltin({
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {
|
|
276
|
+
command: { type: 'string' },
|
|
277
|
+
timeout: { type: 'number' },
|
|
278
|
+
},
|
|
279
|
+
required: ['command'],
|
|
280
|
+
}, {
|
|
281
|
+
name: 'bash',
|
|
282
|
+
description: 'Execute a shell command',
|
|
283
|
+
async execute(params, signal) {
|
|
284
|
+
const chunks = [];
|
|
285
|
+
const result = await remote.exec(String(params.command ?? ''), '.', {
|
|
286
|
+
signal,
|
|
287
|
+
timeout: typeof params.timeout === 'number' ? params.timeout : undefined,
|
|
288
|
+
onData(data) {
|
|
289
|
+
chunks.push(data);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
await runtime.syncLocalWorkspace();
|
|
293
|
+
return `${Buffer.concat(chunks).toString('utf8')}${result.exitCode != null ? `\n\n(exit ${result.exitCode})` : ''}`.trim();
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
addBuiltin({
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {
|
|
299
|
+
pattern: { type: 'string' },
|
|
300
|
+
path: { type: 'string' },
|
|
301
|
+
glob: { type: 'string' },
|
|
302
|
+
ignoreCase: { type: 'boolean' },
|
|
303
|
+
literal: { type: 'boolean' },
|
|
304
|
+
context: { type: 'number' },
|
|
305
|
+
limit: { type: 'number' },
|
|
306
|
+
},
|
|
307
|
+
required: ['pattern'],
|
|
308
|
+
}, {
|
|
309
|
+
name: 'grep',
|
|
310
|
+
description: 'Search file contents',
|
|
311
|
+
async execute(params) {
|
|
312
|
+
return await remote.grepFiles(String(params.pattern ?? ''), {
|
|
313
|
+
...(params.path ? { path: String(params.path) } : {}),
|
|
314
|
+
...(params.glob ? { glob: String(params.glob) } : {}),
|
|
315
|
+
...(typeof params.ignoreCase === 'boolean' ? { ignoreCase: params.ignoreCase } : {}),
|
|
316
|
+
...(typeof params.literal === 'boolean' ? { literal: params.literal } : {}),
|
|
317
|
+
...(typeof params.context === 'number' ? { context: params.context } : {}),
|
|
318
|
+
...(typeof params.limit === 'number' ? { limit: params.limit } : {}),
|
|
319
|
+
});
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
addBuiltin({
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
pattern: { type: 'string' },
|
|
326
|
+
path: { type: 'string' },
|
|
327
|
+
limit: { type: 'number' },
|
|
328
|
+
},
|
|
329
|
+
required: ['pattern'],
|
|
330
|
+
}, {
|
|
331
|
+
name: 'find',
|
|
332
|
+
description: 'Find files matching a glob pattern',
|
|
333
|
+
async execute(params) {
|
|
334
|
+
return (await remote.findFiles(String(params.pattern ?? ''), params.path ? String(params.path) : '.', typeof params.limit === 'number' ? params.limit : 1_000)).join('\n');
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
addBuiltin({
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
path: { type: 'string' },
|
|
341
|
+
limit: { type: 'number' },
|
|
342
|
+
},
|
|
343
|
+
}, {
|
|
344
|
+
name: 'ls',
|
|
345
|
+
description: 'List directory contents',
|
|
346
|
+
async execute(params) {
|
|
347
|
+
return (await remote.listDirectory(params.path ? String(params.path) : '.', typeof params.limit === 'number' ? params.limit : 500)).join('\n');
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
for (const schema of ctx.registeredTools) {
|
|
351
|
+
if (!includedTools.has(schema.name)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
definitions.push(defineTool({
|
|
355
|
+
name: schema.name,
|
|
356
|
+
label: schema.name,
|
|
357
|
+
description: schema.description || '',
|
|
358
|
+
parameters: Type.Unsafe(schema.inputSchema),
|
|
359
|
+
async execute(toolCallId, params) {
|
|
360
|
+
const inputParams = params;
|
|
361
|
+
const requestId = toolCallId;
|
|
362
|
+
const output = await runtime.waitForToolResult(requestId, {
|
|
363
|
+
toolName: schema.name,
|
|
364
|
+
toolCallId,
|
|
365
|
+
input: inputParams,
|
|
366
|
+
});
|
|
367
|
+
runtime.pendingToolResults.delete(requestId);
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: 'text', text: serializeToolOutput(runtime.buildVisibleToolOutputForAgent(output)) }],
|
|
370
|
+
details: {},
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
if (ctx.requestHostAccess && includedTools.has(REQUEST_HOST_ACCESS_TOOL_NAME)) {
|
|
376
|
+
definitions.push(defineTool({
|
|
377
|
+
name: REQUEST_HOST_ACCESS_TOOL_NAME,
|
|
378
|
+
label: REQUEST_HOST_ACCESS_TOOL_NAME,
|
|
379
|
+
description: 'Request access to an external host when sandbox network access is restricted.',
|
|
380
|
+
parameters: Type.Unsafe({
|
|
381
|
+
type: 'object',
|
|
382
|
+
properties: {
|
|
383
|
+
host: { type: 'string' },
|
|
384
|
+
access: { type: 'string', enum: ['proxy', 'direct'] },
|
|
385
|
+
reason: { type: 'string' },
|
|
386
|
+
},
|
|
387
|
+
required: ['host'],
|
|
388
|
+
}),
|
|
389
|
+
async execute(_toolCallId, params) {
|
|
390
|
+
const inputParams = params;
|
|
391
|
+
const result = await ctx.requestHostAccess?.({
|
|
392
|
+
host: String(inputParams.host),
|
|
393
|
+
access: inputParams.access === 'proxy' || inputParams.access === 'direct' ? inputParams.access : undefined,
|
|
394
|
+
...(typeof inputParams.reason === 'string' ? { reason: inputParams.reason } : {}),
|
|
395
|
+
});
|
|
396
|
+
return {
|
|
397
|
+
content: [{ type: 'text', text: serializeToolOutput(result) }],
|
|
398
|
+
details: {},
|
|
399
|
+
};
|
|
400
|
+
},
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
if (input.allowDelegation && ctx.agents && Object.keys(ctx.agents).length > 0) {
|
|
404
|
+
definitions.push(defineTool({
|
|
405
|
+
name: 'delegate_task',
|
|
406
|
+
label: 'delegate_task',
|
|
407
|
+
description: createDelegateToolDescription(ctx.agents),
|
|
408
|
+
parameters: Type.Unsafe({
|
|
409
|
+
type: 'object',
|
|
410
|
+
properties: {
|
|
411
|
+
agent: { type: 'string', enum: Object.keys(ctx.agents) },
|
|
412
|
+
task: { type: 'string' },
|
|
413
|
+
},
|
|
414
|
+
required: ['agent', 'task'],
|
|
415
|
+
additionalProperties: false,
|
|
416
|
+
}),
|
|
417
|
+
async execute(toolCallId, params) {
|
|
418
|
+
const inputParams = params;
|
|
419
|
+
const agentName = String(inputParams.agent ?? '');
|
|
420
|
+
const task = String(inputParams.task ?? '');
|
|
421
|
+
const definition = ctx.agents?.[agentName];
|
|
422
|
+
if (!definition || !task || !runtime.logicalWorkDir || !runtime.logicalAgentDir) {
|
|
423
|
+
throw new Error('delegate_task requires a valid agent and task');
|
|
424
|
+
}
|
|
425
|
+
runtime.ctx?.emit(protocolMessage.subagentStart({ agentName, parentToolCallId: toolCallId }));
|
|
426
|
+
let usage;
|
|
427
|
+
let text = '';
|
|
428
|
+
let resolveCompletion;
|
|
429
|
+
const completionPromise = new Promise((resolve) => {
|
|
430
|
+
resolveCompletion = resolve;
|
|
431
|
+
});
|
|
432
|
+
const emitMissingAssistantText = (event) => {
|
|
433
|
+
const fullText = extractAssistantText(event.message);
|
|
434
|
+
if (!fullText) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
text = fullText;
|
|
438
|
+
};
|
|
439
|
+
try {
|
|
440
|
+
const childSettingsManager = SettingsManager.inMemory();
|
|
441
|
+
const childResourceLoader = new DefaultResourceLoader({
|
|
442
|
+
cwd: runtime.logicalWorkDir,
|
|
443
|
+
agentDir: runtime.logicalAgentDir,
|
|
444
|
+
settingsManager: childSettingsManager,
|
|
445
|
+
systemPromptOverride: (base) => [base, createSubagentPrompt(agentName, definition, definition.tools)].filter(Boolean).join('\n\n') || undefined,
|
|
446
|
+
appendSystemPromptOverride: () => [],
|
|
447
|
+
});
|
|
448
|
+
await childResourceLoader.reload();
|
|
449
|
+
const childTools = await runtime.buildToolDefinitions(ctx, modelRegistry, authStorage, {
|
|
450
|
+
allowDelegation: false,
|
|
451
|
+
toolNames: filterAgentToolNames(['read', 'write', 'edit', 'bash', 'grep', 'find', 'ls', REQUEST_HOST_ACCESS_TOOL_NAME, ...ctx.registeredTools.map((tool) => tool.name)], definition.tools),
|
|
452
|
+
});
|
|
453
|
+
const resolveModel = createPiModelResolver(modelRegistry);
|
|
454
|
+
const resolvedModel = resolveModel({ model: runtime.options?.model }, definition.model);
|
|
455
|
+
const { session } = await createAgentSession({
|
|
456
|
+
cwd: runtime.logicalWorkDir,
|
|
457
|
+
agentDir: runtime.logicalAgentDir,
|
|
458
|
+
authStorage,
|
|
459
|
+
modelRegistry,
|
|
460
|
+
sessionManager: SessionManager.inMemory(runtime.logicalWorkDir),
|
|
461
|
+
settingsManager: childSettingsManager,
|
|
462
|
+
resourceLoader: childResourceLoader,
|
|
463
|
+
customTools: childTools,
|
|
464
|
+
tools: childTools.map((tool) => tool.name),
|
|
465
|
+
...(runtime.options?.thinkingLevel ? { thinkingLevel: runtime.options.thinkingLevel } : {}),
|
|
466
|
+
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
467
|
+
});
|
|
468
|
+
const unsubscribe = session.subscribe((rawEvent) => {
|
|
469
|
+
const event = parseNativeEvent(rawEvent);
|
|
470
|
+
if (event.type === 'message_update' &&
|
|
471
|
+
event.assistantMessageEvent?.type === 'text_delta' &&
|
|
472
|
+
typeof event.assistantMessageEvent.delta === 'string') {
|
|
473
|
+
text += event.assistantMessageEvent.delta;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (event.type === 'message_end' || event.type === 'turn_end') {
|
|
477
|
+
emitMissingAssistantText(event);
|
|
478
|
+
}
|
|
479
|
+
if (event.type === 'agent_end' || event.type === 'turn_end') {
|
|
480
|
+
resolveCompletion?.();
|
|
481
|
+
resolveCompletion = undefined;
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
try {
|
|
485
|
+
await session.prompt(task);
|
|
486
|
+
await session.agent.waitForIdle();
|
|
487
|
+
await completionPromise;
|
|
488
|
+
}
|
|
489
|
+
finally {
|
|
490
|
+
unsubscribe();
|
|
491
|
+
session.dispose();
|
|
492
|
+
}
|
|
493
|
+
const stats = session.getSessionStats?.();
|
|
494
|
+
usage = {
|
|
495
|
+
inputTokens: stats?.promptTokens ?? stats?.inputTokens ?? 0,
|
|
496
|
+
outputTokens: stats?.completionTokens ?? stats?.outputTokens ?? 0,
|
|
497
|
+
...(typeof stats?.cost === 'number' ? { costUsd: stats.cost } : {}),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
finally {
|
|
501
|
+
runtime.ctx?.emit(protocolMessage.subagentFinish({ agentName, parentToolCallId: toolCallId, output: { agent: agentName, text }, ...(usage ? { usage } : {}) }));
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
content: [{ type: 'text', text: serializeToolOutput({ agent: agentName, text }) }],
|
|
505
|
+
details: {},
|
|
506
|
+
};
|
|
507
|
+
},
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
return definitions;
|
|
511
|
+
}
|
|
512
|
+
async start(ctx) {
|
|
513
|
+
this.ctx = ctx;
|
|
514
|
+
this.currentInstructions = ctx.instructions;
|
|
515
|
+
await this.ensureLocalMirror(ctx);
|
|
516
|
+
if (!this.localWorkDir || !this.localAgentDir || !this.localSessionDir || !this.logicalWorkDir || !this.logicalAgentDir || !this.logicalSessionDir) {
|
|
517
|
+
throw new Error('Split Pi local mirror was not initialized');
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const sandboxConfig = withVercelSandboxBackend(ctx.sandboxConfig);
|
|
521
|
+
if (sandboxConfig?.httpHandler) {
|
|
522
|
+
this.replayProxies = await createReplayProxies(sandboxConfig.httpHandler, resolveReplayProxyTargets(this.options, process.env));
|
|
523
|
+
}
|
|
524
|
+
const { workDir } = getSandboxRuntimeLayout('pi', ctx.sessionId, sandboxConfig);
|
|
525
|
+
this.splitSandbox = await startSplitSandbox(ctx, {
|
|
526
|
+
adapterName: 'pi',
|
|
527
|
+
sessionId: ctx.sessionId,
|
|
528
|
+
imageName: this.splitSandboxImageName,
|
|
529
|
+
sandboxConfig,
|
|
530
|
+
sandboxFiles: buildRemotePiMetadata(ctx, workDir),
|
|
531
|
+
});
|
|
532
|
+
this.splitSandbox.networkBroker.notifyPause = () => {
|
|
533
|
+
this.resolvePromptPause?.();
|
|
534
|
+
this.resolvePromptPause = undefined;
|
|
535
|
+
};
|
|
536
|
+
this.sessionFile = this.importedState?.sessionFile;
|
|
537
|
+
await this.syncLocalWorkspace();
|
|
538
|
+
this.mountWorkspaceVfs();
|
|
539
|
+
await this.initializeHostSession(ctx);
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
this.unsubscribeSession?.();
|
|
543
|
+
this.unsubscribeSession = null;
|
|
544
|
+
this.session?.dispose();
|
|
545
|
+
this.session = null;
|
|
546
|
+
this.resourceLoader = null;
|
|
547
|
+
this.authStorage = null;
|
|
548
|
+
this.modelRegistry = null;
|
|
549
|
+
this.settingsManager = null;
|
|
550
|
+
if (this.splitSandbox) {
|
|
551
|
+
await stopSplitSandbox(this.splitSandbox);
|
|
552
|
+
}
|
|
553
|
+
this.splitSandbox = null;
|
|
554
|
+
this.workspaceVfs.unmount();
|
|
555
|
+
await this.closeReplayProxies();
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async prompt(message, ctx) {
|
|
560
|
+
if (!this.session || !this.resourceLoader) {
|
|
561
|
+
throw new Error('Split Pi runtime has not started');
|
|
562
|
+
}
|
|
563
|
+
this.attachContext(ctx);
|
|
564
|
+
this.currentInstructions = ctx.instructions;
|
|
565
|
+
await this.syncLocalWorkspace();
|
|
566
|
+
await this.resourceLoader.reload();
|
|
567
|
+
// Seed the prepare-step bridge with the host's initial step config so
|
|
568
|
+
// Pi's `before_agent_start` handler can append per-step instructions to
|
|
569
|
+
// the system prompt for turn 0. Subsequent turns are resolved by the
|
|
570
|
+
// extension via `ctx.resolvePrepareStep(turnIndex)` on `turn_start`.
|
|
571
|
+
this.prepareStepBridge.reset();
|
|
572
|
+
if (ctx.resolvePrepareStep) {
|
|
573
|
+
try {
|
|
574
|
+
this.prepareStepBridge.setInitialConfig(await ctx.resolvePrepareStep(0));
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
// Host failed to resolve an initial step — fall back to no overrides.
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
this.turnTerminalError = undefined;
|
|
581
|
+
this.promptStarted = false;
|
|
582
|
+
this.streamedAssistantText = '';
|
|
583
|
+
try {
|
|
584
|
+
const session = this.session;
|
|
585
|
+
const completionPromise = new Promise((resolve) => {
|
|
586
|
+
this.resolvePromptCompletion = resolve;
|
|
587
|
+
});
|
|
588
|
+
const promptPromise = (async () => {
|
|
589
|
+
await session.prompt(message);
|
|
590
|
+
await session.agent.waitForIdle();
|
|
591
|
+
await completionPromise;
|
|
592
|
+
})();
|
|
593
|
+
let backgroundMode = false;
|
|
594
|
+
const emitTurnOutcome = () => {
|
|
595
|
+
const stats = this.session?.getSessionStats?.();
|
|
596
|
+
if (this.turnTerminalError) {
|
|
597
|
+
ctx.emit(protocolMessage.error({ message: this.turnTerminalError }));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
ctx.emit(protocolMessage.finish({
|
|
601
|
+
finishReason: 'stop',
|
|
602
|
+
usage: {
|
|
603
|
+
inputTokens: stats?.promptTokens ?? stats?.inputTokens ?? 0,
|
|
604
|
+
outputTokens: stats?.completionTokens ?? stats?.outputTokens ?? 0,
|
|
605
|
+
...(typeof stats?.cost === 'number' ? { costUsd: stats.cost } : {}),
|
|
606
|
+
},
|
|
607
|
+
}));
|
|
608
|
+
};
|
|
609
|
+
void promptPromise
|
|
610
|
+
.then(() => {
|
|
611
|
+
if (backgroundMode) {
|
|
612
|
+
emitTurnOutcome();
|
|
613
|
+
}
|
|
614
|
+
}, (error) => {
|
|
615
|
+
if (backgroundMode) {
|
|
616
|
+
ctx.emit(protocolMessage.error({ message: this.turnTerminalError ?? getErrorText(error) }));
|
|
617
|
+
}
|
|
618
|
+
})
|
|
619
|
+
.finally(() => {
|
|
620
|
+
this.resolvePromptPause = undefined;
|
|
621
|
+
this.resolveTurnTerminalError = undefined;
|
|
622
|
+
this.resolvePromptCompletion = undefined;
|
|
623
|
+
this.promptStarted = false;
|
|
624
|
+
this.streamedAssistantText = '';
|
|
625
|
+
});
|
|
626
|
+
const promptResult = await Promise.race([
|
|
627
|
+
promptPromise.then(() => ({ type: 'complete' })),
|
|
628
|
+
new Promise((resolve) => {
|
|
629
|
+
this.resolveTurnTerminalError = (error) => {
|
|
630
|
+
this.resolveTurnTerminalError = undefined;
|
|
631
|
+
resolve({ type: 'terminal-error', message: error });
|
|
632
|
+
};
|
|
633
|
+
}),
|
|
634
|
+
new Promise((resolve) => {
|
|
635
|
+
this.resolvePromptPause = () => {
|
|
636
|
+
this.resolvePromptPause = undefined;
|
|
637
|
+
resolve({ type: 'paused' });
|
|
638
|
+
};
|
|
639
|
+
}),
|
|
640
|
+
]);
|
|
641
|
+
if (promptResult.type === 'paused') {
|
|
642
|
+
backgroundMode = true;
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (promptResult.type === 'terminal-error' || this.turnTerminalError) {
|
|
646
|
+
ctx.emit(protocolMessage.error({ message: promptResult.type === 'terminal-error' ? promptResult.message : (this.turnTerminalError ?? 'Unknown error') }));
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
emitTurnOutcome();
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
ctx.emit(protocolMessage.error({ message: this.turnTerminalError ?? getErrorText(error) }));
|
|
653
|
+
}
|
|
654
|
+
finally {
|
|
655
|
+
this.resolvePromptPause = undefined;
|
|
656
|
+
this.resolveTurnTerminalError = undefined;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
settlePending(reason) {
|
|
660
|
+
for (const resolve of this.pendingToolResults.values()) {
|
|
661
|
+
resolve({ error: reason });
|
|
662
|
+
}
|
|
663
|
+
this.pendingToolResults.clear();
|
|
664
|
+
for (const resolve of this.pendingToolDecisions.values()) {
|
|
665
|
+
resolve({ approved: false, reason });
|
|
666
|
+
}
|
|
667
|
+
this.pendingToolDecisions.clear();
|
|
668
|
+
}
|
|
669
|
+
async closeReplayProxies() {
|
|
670
|
+
if (this.replayProxies.length === 0) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const proxies = this.replayProxies;
|
|
674
|
+
this.replayProxies = [];
|
|
675
|
+
await Promise.all(proxies.map((proxy) => proxy.close()));
|
|
676
|
+
}
|
|
677
|
+
attachContext(ctx) {
|
|
678
|
+
this.ctx = ctx;
|
|
679
|
+
this.prepareStepBridge.setCtx(ctx);
|
|
680
|
+
if (this.splitSandbox) {
|
|
681
|
+
attachSplitSandboxContext(this.splitSandbox, ctx);
|
|
682
|
+
}
|
|
683
|
+
ctx.sendToolResult = (requestId, output) => {
|
|
684
|
+
this.prepareStepBridge.setResolved(this.buildCurrentPrepareStepConfig(ctx));
|
|
685
|
+
this.pendingToolResults.get(requestId)?.(output);
|
|
686
|
+
this.pendingToolResults.delete(requestId);
|
|
687
|
+
};
|
|
688
|
+
ctx.sendToolDecision = (requestId, approved, reason) => {
|
|
689
|
+
this.pendingToolDecisions.get(requestId)?.({ approved, reason });
|
|
690
|
+
this.pendingToolDecisions.delete(requestId);
|
|
691
|
+
};
|
|
692
|
+
ctx.sendUserMessage = (text) => {
|
|
693
|
+
void this.session?.steer?.(text);
|
|
694
|
+
};
|
|
695
|
+
ctx.sendCompact = () => {
|
|
696
|
+
void this.session?.compact?.();
|
|
697
|
+
};
|
|
698
|
+
ctx.abort = () => {
|
|
699
|
+
void this.session?.abort?.();
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
buildCurrentPrepareStepConfig(ctx) {
|
|
703
|
+
return {
|
|
704
|
+
...(ctx.activeTools !== undefined ? { activeTools: [...ctx.activeTools] } : {}),
|
|
705
|
+
activeSkillNames: (ctx.activeSkills ?? ctx.skills ?? []).map((skill) => skill.name),
|
|
706
|
+
forcedSkillNames: (ctx.forcedSkills ?? []).map((skill) => skill.name),
|
|
707
|
+
...(ctx.instructions !== undefined ? { instructions: ctx.instructions } : {}),
|
|
708
|
+
...(ctx.responseFormat !== undefined ? { responseFormat: ctx.responseFormat } : {}),
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
buildVisibleToolOutputForAgent(output) {
|
|
712
|
+
const config = this.prepareStepBridge.getResolved();
|
|
713
|
+
const nextStep = {
|
|
714
|
+
note: 'Use this nextStep guidance for your next action.',
|
|
715
|
+
};
|
|
716
|
+
if (config?.instructions) {
|
|
717
|
+
nextStep.instructions = config.instructions;
|
|
718
|
+
}
|
|
719
|
+
if (config?.activeTools?.length) {
|
|
720
|
+
nextStep.activeTools = [...config.activeTools];
|
|
721
|
+
}
|
|
722
|
+
if (config?.activeSkillNames?.length) {
|
|
723
|
+
nextStep.activeSkills = [...config.activeSkillNames];
|
|
724
|
+
}
|
|
725
|
+
if (config?.forcedSkillNames?.length) {
|
|
726
|
+
nextStep.forcedSkills = [...config.forcedSkillNames];
|
|
727
|
+
}
|
|
728
|
+
return Object.keys(nextStep).length === 1 ? output : { result: output, nextStep };
|
|
729
|
+
}
|
|
730
|
+
bindSession(ctx, session) {
|
|
731
|
+
this.session = session;
|
|
732
|
+
this.unsubscribeSession = session.subscribe((rawEvent) => {
|
|
733
|
+
const event = parseNativeEvent(rawEvent);
|
|
734
|
+
const terminalError = getPiTerminalError(event);
|
|
735
|
+
if (terminalError) {
|
|
736
|
+
this.turnTerminalError = terminalError;
|
|
737
|
+
this.resolveTurnTerminalError?.(terminalError);
|
|
738
|
+
this.resolveTurnTerminalError = undefined;
|
|
739
|
+
}
|
|
740
|
+
if (event.type === 'message_update') {
|
|
741
|
+
if (!this.promptStarted) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const update = event.assistantMessageEvent;
|
|
745
|
+
if (update?.type === 'text_delta' && typeof update.delta === 'string') {
|
|
746
|
+
this.streamedAssistantText += update.delta;
|
|
747
|
+
this.ctx?.emit(protocolMessage.textDelta({ delta: update.delta }));
|
|
748
|
+
}
|
|
749
|
+
if (update?.type === 'thinking_delta' && typeof update.delta === 'string') {
|
|
750
|
+
this.ctx?.emit(protocolMessage.reasoningDelta({ delta: update.delta }));
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (event.type === 'turn_start' || (event.type === 'message_start' && event.message?.role === 'assistant')) {
|
|
755
|
+
this.promptStarted = true;
|
|
756
|
+
this.streamedAssistantText = '';
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if ((event.type === 'message_end' || event.type === 'turn_end') && this.promptStarted) {
|
|
760
|
+
const fullText = extractAssistantText(event.message);
|
|
761
|
+
if (fullText.startsWith(this.streamedAssistantText) && fullText.length > this.streamedAssistantText.length) {
|
|
762
|
+
const missingText = fullText.slice(this.streamedAssistantText.length);
|
|
763
|
+
this.streamedAssistantText = fullText;
|
|
764
|
+
this.ctx?.emit(protocolMessage.textDelta({ delta: missingText }));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if ((event.type === 'agent_end' || event.type === 'turn_end') && this.promptStarted) {
|
|
768
|
+
this.resolvePromptCompletion?.();
|
|
769
|
+
this.resolvePromptCompletion = undefined;
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (event.type === 'tool_call' && event.toolCallId && event.toolName) {
|
|
773
|
+
this.observedToolNames.set(event.toolCallId, event.toolName);
|
|
774
|
+
this.ctx?.emit(protocolMessage.toolCall({
|
|
775
|
+
requestId: event.toolCallId,
|
|
776
|
+
toolName: event.toolName,
|
|
777
|
+
toolCallId: event.toolCallId,
|
|
778
|
+
input: event.input,
|
|
779
|
+
observeOnly: true,
|
|
780
|
+
}));
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (event.type === 'tool_result' && event.toolCallId) {
|
|
784
|
+
const toolName = event.toolName ?? this.observedToolNames.get(event.toolCallId);
|
|
785
|
+
if (!toolName) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
this.ctx?.emit(protocolMessage.toolResult({ toolName, toolCallId: event.toolCallId, output: event.content }));
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
this.attachContext(ctx);
|
|
792
|
+
}
|
|
793
|
+
async rebuildSession(ctx) {
|
|
794
|
+
if (!this.logicalWorkDir ||
|
|
795
|
+
!this.logicalAgentDir ||
|
|
796
|
+
!this.logicalSessionDir ||
|
|
797
|
+
!this.resourceLoader ||
|
|
798
|
+
!this.authStorage ||
|
|
799
|
+
!this.modelRegistry ||
|
|
800
|
+
!this.settingsManager) {
|
|
801
|
+
throw new Error('Split Pi runtime session dependencies are not initialized');
|
|
802
|
+
}
|
|
803
|
+
this.unsubscribeSession?.();
|
|
804
|
+
this.unsubscribeSession = null;
|
|
805
|
+
this.session?.dispose();
|
|
806
|
+
this.session = null;
|
|
807
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
808
|
+
const { SessionManager, createAgentSession } = await loadPiCodingAgent();
|
|
809
|
+
const sessionManager = this.sessionFile && existsSync(this.sessionFile)
|
|
810
|
+
? SessionManager.open(this.sessionFile, this.logicalSessionDir, this.logicalWorkDir)
|
|
811
|
+
: SessionManager.create(this.logicalWorkDir, this.logicalSessionDir);
|
|
812
|
+
this.sessionFile = sessionManager.getSessionFile?.();
|
|
813
|
+
const tools = await this.buildToolDefinitions(ctx, this.modelRegistry, this.authStorage, {
|
|
814
|
+
allowDelegation: true,
|
|
815
|
+
});
|
|
816
|
+
const resolveModel = createPiModelResolver(this.modelRegistry, this.resolverEnv);
|
|
817
|
+
const resolvedModel = resolveModel({ model: this.options?.model });
|
|
818
|
+
const { session } = await createAgentSession({
|
|
819
|
+
cwd: this.logicalWorkDir,
|
|
820
|
+
agentDir: this.logicalAgentDir,
|
|
821
|
+
authStorage: this.authStorage,
|
|
822
|
+
modelRegistry: this.modelRegistry,
|
|
823
|
+
sessionManager,
|
|
824
|
+
settingsManager: this.settingsManager,
|
|
825
|
+
resourceLoader: this.resourceLoader,
|
|
826
|
+
customTools: tools,
|
|
827
|
+
tools: tools.map((tool) => tool.name),
|
|
828
|
+
...(this.options?.thinkingLevel ? { thinkingLevel: this.options.thinkingLevel } : {}),
|
|
829
|
+
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
830
|
+
});
|
|
831
|
+
this.bindSession(ctx, session);
|
|
832
|
+
}
|
|
833
|
+
async initializeHostSession(ctx) {
|
|
834
|
+
if (!this.logicalWorkDir || !this.logicalAgentDir) {
|
|
835
|
+
throw new Error('Split Pi local mirror was not initialized');
|
|
836
|
+
}
|
|
837
|
+
const { AuthStorage, DefaultResourceLoader, ModelRegistry, SettingsManager } = await loadPiCodingAgent();
|
|
838
|
+
this.settingsManager = SettingsManager.inMemory();
|
|
839
|
+
const authPath = path.join(this.logicalAgentDir, 'auth.json');
|
|
840
|
+
const modelsPath = path.join(this.logicalAgentDir, 'models.json');
|
|
841
|
+
this.authStorage = AuthStorage.create(authPath);
|
|
842
|
+
this.modelRegistry = ModelRegistry.create(this.authStorage, modelsPath);
|
|
843
|
+
this.resolverEnv = configureAuth(this.options, this.authStorage, this.modelRegistry, process.env, Object.fromEntries(this.replayProxies.map((proxy) => [proxy.provider, proxy.baseURL])));
|
|
844
|
+
this.resourceLoader = new DefaultResourceLoader({
|
|
845
|
+
cwd: this.logicalWorkDir,
|
|
846
|
+
agentDir: this.logicalAgentDir,
|
|
847
|
+
settingsManager: this.settingsManager,
|
|
848
|
+
systemPromptOverride: (base) => [base, this.currentInstructions].filter(Boolean).join('\n\n') || undefined,
|
|
849
|
+
appendSystemPromptOverride: () => [],
|
|
850
|
+
extensionFactories: [createPrepareStepExtension(this.prepareStepBridge)],
|
|
851
|
+
});
|
|
852
|
+
await this.resourceLoader.reload();
|
|
853
|
+
await this.rebuildSession(ctx);
|
|
854
|
+
}
|
|
855
|
+
async stop() {
|
|
856
|
+
this.settlePending('Session stopped');
|
|
857
|
+
this.unsubscribeSession?.();
|
|
858
|
+
this.unsubscribeSession = null;
|
|
859
|
+
this.session?.dispose();
|
|
860
|
+
this.session = null;
|
|
861
|
+
this.resourceLoader = null;
|
|
862
|
+
this.authStorage = null;
|
|
863
|
+
this.modelRegistry = null;
|
|
864
|
+
this.settingsManager = null;
|
|
865
|
+
if (this.splitSandbox) {
|
|
866
|
+
await stopSplitSandbox(this.splitSandbox);
|
|
867
|
+
}
|
|
868
|
+
this.splitSandbox = null;
|
|
869
|
+
this.workspaceVfs.unmount();
|
|
870
|
+
await this.closeReplayProxies();
|
|
871
|
+
this.prepareStepBridge.setCtx(null);
|
|
872
|
+
this.prepareStepBridge.reset();
|
|
873
|
+
}
|
|
874
|
+
async detach() {
|
|
875
|
+
this.settlePending('Session detached');
|
|
876
|
+
this.unsubscribeSession?.();
|
|
877
|
+
this.unsubscribeSession = null;
|
|
878
|
+
this.session?.dispose();
|
|
879
|
+
this.session = null;
|
|
880
|
+
this.resourceLoader = null;
|
|
881
|
+
this.authStorage = null;
|
|
882
|
+
this.modelRegistry = null;
|
|
883
|
+
this.settingsManager = null;
|
|
884
|
+
if (this.splitSandbox) {
|
|
885
|
+
detachSplitSandbox(this.splitSandbox);
|
|
886
|
+
}
|
|
887
|
+
this.splitSandbox = null;
|
|
888
|
+
this.workspaceVfs.unmount();
|
|
889
|
+
await this.closeReplayProxies();
|
|
890
|
+
this.prepareStepBridge.setCtx(null);
|
|
891
|
+
this.prepareStepBridge.reset();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
//# sourceMappingURL=split-runtime.js.map
|