@automatalabs/pi-acp 0.1.0
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 +202 -0
- package/README.md +57 -0
- package/dist/agent.d.ts +87 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +398 -0
- package/dist/auth.d.ts +4 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +40 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +46 -0
- package/dist/deps.d.ts +22 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +39 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +104 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/lib.d.ts +4 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +2 -0
- package/dist/mcp-bridge.d.ts +58 -0
- package/dist/mcp-bridge.d.ts.map +1 -0
- package/dist/mcp-bridge.js +240 -0
- package/dist/permissions.d.ts +11 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +77 -0
- package/dist/prompt-content.d.ts +12 -0
- package/dist/prompt-content.d.ts.map +1 -0
- package/dist/prompt-content.js +35 -0
- package/dist/replay.d.ts +4 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +101 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +23 -0
- package/dist/session.d.ts +58 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +414 -0
- package/dist/stop-reason.d.ts +4 -0
- package/dist/stop-reason.d.ts.map +1 -0
- package/dist/stop-reason.js +18 -0
- package/dist/structured-output.d.ts +14 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/structured-output.js +71 -0
- package/dist/translate.d.ts +20 -0
- package/dist/translate.d.ts.map +1 -0
- package/dist/translate.js +110 -0
- package/dist/usage.d.ts +35 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +36 -0
- package/package.json +47 -0
package/dist/agent.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { AUTH_METHODS, authenticateMethod } from "./auth.js";
|
|
4
|
+
import { adapterError, isRequestError, unexpectedError } from "./errors.js";
|
|
5
|
+
import { bridgeMcpServers, disposeMcpBridge, } from "./mcp-bridge.js";
|
|
6
|
+
import { PiSession } from "./session.js";
|
|
7
|
+
import { StructuredOutputState, STRUCTURED_TOOL_NAME } from "./structured-output.js";
|
|
8
|
+
export const PKG_VERSION = String(JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version);
|
|
9
|
+
function validateCwd(cwd) {
|
|
10
|
+
if (!isAbsolute(cwd))
|
|
11
|
+
throw adapterError("invalid_cwd");
|
|
12
|
+
try {
|
|
13
|
+
if (!statSync(cwd).isDirectory())
|
|
14
|
+
throw adapterError("invalid_cwd");
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isRequestError(error))
|
|
18
|
+
throw error;
|
|
19
|
+
throw adapterError("invalid_cwd");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function openSignal(requestSignal) {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const abort = () => controller.abort(requestSignal.reason);
|
|
25
|
+
if (requestSignal.aborted)
|
|
26
|
+
abort();
|
|
27
|
+
else
|
|
28
|
+
requestSignal.addEventListener("abort", abort, { once: true });
|
|
29
|
+
return controller;
|
|
30
|
+
}
|
|
31
|
+
export class PiAcpAgent {
|
|
32
|
+
deps;
|
|
33
|
+
live = new Map();
|
|
34
|
+
opening = new Map();
|
|
35
|
+
openingControllers = new Set();
|
|
36
|
+
openingTasks = new Set();
|
|
37
|
+
tombstones = new Set();
|
|
38
|
+
disposed = false;
|
|
39
|
+
constructor(deps) {
|
|
40
|
+
this.deps = deps;
|
|
41
|
+
}
|
|
42
|
+
initialize(_context) {
|
|
43
|
+
return {
|
|
44
|
+
protocolVersion: 1,
|
|
45
|
+
agentInfo: {
|
|
46
|
+
name: "@automatalabs/pi-acp",
|
|
47
|
+
title: "pi coding agent",
|
|
48
|
+
version: PKG_VERSION,
|
|
49
|
+
},
|
|
50
|
+
agentCapabilities: {
|
|
51
|
+
loadSession: true,
|
|
52
|
+
promptCapabilities: { image: true },
|
|
53
|
+
mcpCapabilities: {},
|
|
54
|
+
sessionCapabilities: { resume: {}, fork: {}, list: {}, close: {} },
|
|
55
|
+
_meta: { "@automatalabs/pi-acp": { outputSchema: true } },
|
|
56
|
+
},
|
|
57
|
+
authMethods: AUTH_METHODS,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
authenticate(context) {
|
|
61
|
+
return authenticateMethod(context.params.methodId);
|
|
62
|
+
}
|
|
63
|
+
ensureMayOpen(id) {
|
|
64
|
+
if (this.disposed)
|
|
65
|
+
throw adapterError("internal_error");
|
|
66
|
+
if (this.tombstones.has(id))
|
|
67
|
+
throw adapterError("session_terminated");
|
|
68
|
+
if (this.live.has(id) || this.opening.has(id))
|
|
69
|
+
throw adapterError("session_already_open");
|
|
70
|
+
}
|
|
71
|
+
reserve(id, controller) {
|
|
72
|
+
this.ensureMayOpen(id);
|
|
73
|
+
this.opening.set(id, controller);
|
|
74
|
+
}
|
|
75
|
+
beginOpening(requestSignal) {
|
|
76
|
+
if (this.disposed)
|
|
77
|
+
throw adapterError("internal_error");
|
|
78
|
+
const controller = openSignal(requestSignal);
|
|
79
|
+
this.openingControllers.add(controller);
|
|
80
|
+
return { controller };
|
|
81
|
+
}
|
|
82
|
+
track(task) {
|
|
83
|
+
this.openingTasks.add(task);
|
|
84
|
+
task.finally(() => this.openingTasks.delete(task)).catch(() => undefined);
|
|
85
|
+
return task;
|
|
86
|
+
}
|
|
87
|
+
gate(opening) {
|
|
88
|
+
opening.controller.signal.throwIfAborted();
|
|
89
|
+
if (this.disposed || (opening.id !== undefined && this.tombstones.has(opening.id))) {
|
|
90
|
+
throw opening.controller.signal.reason ?? adapterError("internal_error");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async construct(opening, manager, cwd, client, mcpServers, replay, preconnected) {
|
|
94
|
+
const id = manager.getSessionId();
|
|
95
|
+
if (opening.id === undefined) {
|
|
96
|
+
this.reserve(id, opening.controller);
|
|
97
|
+
opening.id = id;
|
|
98
|
+
}
|
|
99
|
+
let bridge = preconnected;
|
|
100
|
+
let pi;
|
|
101
|
+
let wrapper;
|
|
102
|
+
try {
|
|
103
|
+
this.gate(opening);
|
|
104
|
+
bridge ??= await bridgeMcpServers(mcpServers, opening.controller.signal, this.deps);
|
|
105
|
+
this.gate(opening);
|
|
106
|
+
const structured = new StructuredOutputState();
|
|
107
|
+
const created = await this.deps.createAgentSession({
|
|
108
|
+
cwd,
|
|
109
|
+
sessionManager: manager,
|
|
110
|
+
modelRuntime: this.deps.modelRuntime,
|
|
111
|
+
customTools: [...bridge.tools, structured.tool],
|
|
112
|
+
});
|
|
113
|
+
pi = created.session;
|
|
114
|
+
this.gate(opening);
|
|
115
|
+
wrapper = new PiSession({
|
|
116
|
+
sessionId: id,
|
|
117
|
+
session: pi,
|
|
118
|
+
manager,
|
|
119
|
+
client,
|
|
120
|
+
deps: this.deps,
|
|
121
|
+
mcpClients: bridge.clients,
|
|
122
|
+
failedMcpResults: bridge.failedResults,
|
|
123
|
+
structured,
|
|
124
|
+
onWedged: (sessionId, session) => this.terminateWedged(sessionId, session),
|
|
125
|
+
});
|
|
126
|
+
structured.install(pi);
|
|
127
|
+
const names = new Set(pi.getAllTools().map(({ name }) => name));
|
|
128
|
+
if (!names.has(STRUCTURED_TOOL_NAME))
|
|
129
|
+
throw adapterError("structured_tool_collision");
|
|
130
|
+
const missingAlias = bridge.aliases.find((alias) => !names.has(alias));
|
|
131
|
+
if (missingAlias) {
|
|
132
|
+
throw adapterError("mcp_init_error", {
|
|
133
|
+
server: bridge.aliasServers.get(missingAlias) ?? "unknown",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (replay)
|
|
137
|
+
await wrapper.replay(manager.getBranch());
|
|
138
|
+
this.gate(opening);
|
|
139
|
+
this.live.set(id, wrapper);
|
|
140
|
+
this.opening.delete(id);
|
|
141
|
+
return wrapper;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (wrapper)
|
|
145
|
+
await wrapper.disposeResources();
|
|
146
|
+
else {
|
|
147
|
+
if (pi) {
|
|
148
|
+
try {
|
|
149
|
+
await pi.dispose();
|
|
150
|
+
}
|
|
151
|
+
catch (disposeError) {
|
|
152
|
+
console.error("pi-acp rollback dispose error:", disposeError);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (bridge)
|
|
156
|
+
await disposeMcpBridge(bridge.clients, this.deps);
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
if (opening.id !== undefined && this.opening.get(opening.id) === opening.controller) {
|
|
162
|
+
this.opening.delete(opening.id);
|
|
163
|
+
}
|
|
164
|
+
this.openingControllers.delete(opening.controller);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
openingError(error) {
|
|
168
|
+
if (isRequestError(error))
|
|
169
|
+
throw error;
|
|
170
|
+
throw unexpectedError(error);
|
|
171
|
+
}
|
|
172
|
+
newSession(context) {
|
|
173
|
+
validateCwd(context.params.cwd);
|
|
174
|
+
let manager;
|
|
175
|
+
try {
|
|
176
|
+
manager = this.deps.sessions.create(context.params.cwd, this.deps.sessionDir);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return this.openingError(error);
|
|
180
|
+
}
|
|
181
|
+
const opening = this.beginOpening(context.signal);
|
|
182
|
+
try {
|
|
183
|
+
this.reserve(manager.getSessionId(), opening.controller);
|
|
184
|
+
opening.id = manager.getSessionId();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
this.openingControllers.delete(opening.controller);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
const task = this.construct(opening, manager, context.params.cwd, context.client, context.params.mcpServers, false).then((session) => ({ sessionId: session.sessionId, configOptions: session.configOptions(), modes: null }))
|
|
191
|
+
.catch((error) => this.openingError(error));
|
|
192
|
+
return this.track(task);
|
|
193
|
+
}
|
|
194
|
+
reattach(context, replay) {
|
|
195
|
+
validateCwd(context.params.cwd);
|
|
196
|
+
const opening = this.beginOpening(context.signal);
|
|
197
|
+
try {
|
|
198
|
+
this.reserve(context.params.sessionId, opening.controller);
|
|
199
|
+
opening.id = context.params.sessionId;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
this.openingControllers.delete(opening.controller);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
const task = (async () => {
|
|
206
|
+
try {
|
|
207
|
+
this.gate(opening);
|
|
208
|
+
const infos = await this.deps.sessions.list(context.params.cwd, this.deps.sessionDir);
|
|
209
|
+
this.gate(opening);
|
|
210
|
+
const info = infos.find(({ id }) => id === context.params.sessionId);
|
|
211
|
+
if (!info)
|
|
212
|
+
throw adapterError("unknown_session");
|
|
213
|
+
let manager;
|
|
214
|
+
try {
|
|
215
|
+
manager = this.deps.sessions.open(info.path, this.deps.sessionDir);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
throw adapterError("session_corrupt");
|
|
219
|
+
}
|
|
220
|
+
const session = await this.construct(opening, manager, context.params.cwd, context.client, context.params.mcpServers ?? [], replay);
|
|
221
|
+
return { configOptions: session.configOptions(), modes: null };
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
if (opening.id !== undefined && this.opening.get(opening.id) === opening.controller) {
|
|
225
|
+
this.opening.delete(opening.id);
|
|
226
|
+
}
|
|
227
|
+
this.openingControllers.delete(opening.controller);
|
|
228
|
+
return this.openingError(error);
|
|
229
|
+
}
|
|
230
|
+
})();
|
|
231
|
+
return this.track(task);
|
|
232
|
+
}
|
|
233
|
+
loadSession(context) {
|
|
234
|
+
return this.reattach(context, true);
|
|
235
|
+
}
|
|
236
|
+
resumeSession(context) {
|
|
237
|
+
return this.reattach(context, false);
|
|
238
|
+
}
|
|
239
|
+
forkSession(context) {
|
|
240
|
+
if (this.tombstones.has(context.params.sessionId))
|
|
241
|
+
throw adapterError("session_terminated");
|
|
242
|
+
const liveSource = this.live.get(context.params.sessionId);
|
|
243
|
+
if (liveSource?.busy)
|
|
244
|
+
throw adapterError("session_busy");
|
|
245
|
+
validateCwd(context.params.cwd);
|
|
246
|
+
const opening = this.beginOpening(context.signal);
|
|
247
|
+
const task = (async () => {
|
|
248
|
+
let bridge;
|
|
249
|
+
try {
|
|
250
|
+
let sourcePath;
|
|
251
|
+
if (liveSource) {
|
|
252
|
+
sourcePath = liveSource.manager.getSessionFile();
|
|
253
|
+
if (!sourcePath || !existsSync(sourcePath))
|
|
254
|
+
throw adapterError("session_not_forkable");
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const all = await this.deps.sessions.listAll(this.deps.sessionDir);
|
|
258
|
+
sourcePath = all.find(({ id }) => id === context.params.sessionId)?.path;
|
|
259
|
+
if (!sourcePath)
|
|
260
|
+
throw adapterError("unknown_session");
|
|
261
|
+
}
|
|
262
|
+
this.gate(opening);
|
|
263
|
+
bridge = await bridgeMcpServers(context.params.mcpServers ?? [], opening.controller.signal, this.deps);
|
|
264
|
+
this.gate(opening);
|
|
265
|
+
if (this.tombstones.has(context.params.sessionId))
|
|
266
|
+
throw adapterError("session_terminated");
|
|
267
|
+
if (liveSource?.busy)
|
|
268
|
+
throw adapterError("session_busy");
|
|
269
|
+
let manager;
|
|
270
|
+
try {
|
|
271
|
+
manager = this.deps.sessions.forkFrom(sourcePath, context.params.cwd, this.deps.sessionDir);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
throw adapterError("session_corrupt");
|
|
275
|
+
}
|
|
276
|
+
this.reserve(manager.getSessionId(), opening.controller);
|
|
277
|
+
opening.id = manager.getSessionId();
|
|
278
|
+
const session = await this.construct(opening, manager, context.params.cwd, context.client, context.params.mcpServers ?? [], false, bridge);
|
|
279
|
+
bridge = undefined;
|
|
280
|
+
return { sessionId: session.sessionId, configOptions: session.configOptions(), modes: null };
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
if (bridge)
|
|
284
|
+
await disposeMcpBridge(bridge.clients, this.deps);
|
|
285
|
+
if (opening.id !== undefined && this.opening.get(opening.id) === opening.controller) {
|
|
286
|
+
this.opening.delete(opening.id);
|
|
287
|
+
}
|
|
288
|
+
this.openingControllers.delete(opening.controller);
|
|
289
|
+
return this.openingError(error);
|
|
290
|
+
}
|
|
291
|
+
})();
|
|
292
|
+
return this.track(task);
|
|
293
|
+
}
|
|
294
|
+
async listSessions(context) {
|
|
295
|
+
if (context.params.cwd !== undefined && context.params.cwd !== null)
|
|
296
|
+
validateCwd(context.params.cwd);
|
|
297
|
+
let offset = 0;
|
|
298
|
+
const cursor = context.params.cursor;
|
|
299
|
+
if (cursor) {
|
|
300
|
+
try {
|
|
301
|
+
const decoded = Buffer.from(cursor, "base64url").toString("utf8");
|
|
302
|
+
if (!/^(0|[1-9][0-9]*)$/.test(decoded))
|
|
303
|
+
throw new Error("noncanonical decimal");
|
|
304
|
+
if (Buffer.from(decoded).toString("base64url") !== cursor)
|
|
305
|
+
throw new Error("noncanonical base64url");
|
|
306
|
+
const value = BigInt(decoded);
|
|
307
|
+
offset = value > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(value);
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
throw adapterError("invalid_cursor");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
let infos;
|
|
314
|
+
try {
|
|
315
|
+
infos = context.params.cwd
|
|
316
|
+
? await this.deps.sessions.list(context.params.cwd, this.deps.sessionDir)
|
|
317
|
+
: await this.deps.sessions.listAll(this.deps.sessionDir);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
throw unexpectedError(error);
|
|
321
|
+
}
|
|
322
|
+
const page = infos.slice(offset, offset + 100);
|
|
323
|
+
const nextOffset = offset + 100;
|
|
324
|
+
return {
|
|
325
|
+
sessions: page.map((info) => ({
|
|
326
|
+
sessionId: info.id,
|
|
327
|
+
cwd: info.cwd,
|
|
328
|
+
title: info.name ?? info.firstMessage,
|
|
329
|
+
})),
|
|
330
|
+
...(nextOffset < infos.length
|
|
331
|
+
? { nextCursor: Buffer.from(String(nextOffset)).toString("base64url") }
|
|
332
|
+
: {}),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async closeSession(context) {
|
|
336
|
+
const controller = this.opening.get(context.params.sessionId);
|
|
337
|
+
if (controller) {
|
|
338
|
+
controller.abort(new Error("session closed while opening"));
|
|
339
|
+
return {};
|
|
340
|
+
}
|
|
341
|
+
const session = this.live.get(context.params.sessionId);
|
|
342
|
+
if (!session)
|
|
343
|
+
return {};
|
|
344
|
+
try {
|
|
345
|
+
await session.dispose();
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
console.error("pi-acp close error:", error);
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
if (this.live.get(context.params.sessionId) === session)
|
|
352
|
+
this.live.delete(context.params.sessionId);
|
|
353
|
+
}
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
requireLive(id) {
|
|
357
|
+
if (this.tombstones.has(id))
|
|
358
|
+
throw adapterError("session_terminated");
|
|
359
|
+
const session = this.live.get(id);
|
|
360
|
+
if (!session)
|
|
361
|
+
throw adapterError("unknown_session");
|
|
362
|
+
return session;
|
|
363
|
+
}
|
|
364
|
+
setConfigOption(context) {
|
|
365
|
+
const session = this.requireLive(context.params.sessionId);
|
|
366
|
+
return session.setConfig(context.params.configId, context.params.value)
|
|
367
|
+
.then((configOptions) => ({ configOptions }))
|
|
368
|
+
.catch((error) => {
|
|
369
|
+
if (isRequestError(error))
|
|
370
|
+
throw error;
|
|
371
|
+
throw unexpectedError(error);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
prompt(context) {
|
|
375
|
+
return this.requireLive(context.params.sessionId).prompt(context.params, context.signal);
|
|
376
|
+
}
|
|
377
|
+
cancel(context) {
|
|
378
|
+
this.live.get(context.params.sessionId)?.cancel();
|
|
379
|
+
}
|
|
380
|
+
async terminateWedged(id, session) {
|
|
381
|
+
this.tombstones.add(id);
|
|
382
|
+
if (this.live.get(id) === session)
|
|
383
|
+
this.live.delete(id);
|
|
384
|
+
await session.disposeResources();
|
|
385
|
+
}
|
|
386
|
+
async dispose() {
|
|
387
|
+
if (this.disposed)
|
|
388
|
+
return;
|
|
389
|
+
this.disposed = true;
|
|
390
|
+
for (const controller of this.openingControllers)
|
|
391
|
+
controller.abort(new Error("agent disposed"));
|
|
392
|
+
await Promise.allSettled([...this.openingTasks]);
|
|
393
|
+
const sessions = [...this.live.values()];
|
|
394
|
+
this.live.clear();
|
|
395
|
+
await Promise.allSettled(sessions.map((session) => session.dispose()));
|
|
396
|
+
this.tombstones.clear();
|
|
397
|
+
}
|
|
398
|
+
}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAG3D,eAAO,MAAM,YAAY,EAAE,UAAU,EAgCpC,CAAC;AAIF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAG1E"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { adapterError } from "./errors.js";
|
|
2
|
+
export const AUTH_METHODS = [
|
|
3
|
+
{
|
|
4
|
+
id: "anthropic-api-key",
|
|
5
|
+
name: "Anthropic API key",
|
|
6
|
+
type: "env_var",
|
|
7
|
+
vars: [{ name: "ANTHROPIC_API_KEY", secret: true }],
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "openai-api-key",
|
|
11
|
+
name: "OpenAI API key",
|
|
12
|
+
type: "env_var",
|
|
13
|
+
vars: [{ name: "OPENAI_API_KEY", secret: true }],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "gemini-api-key",
|
|
17
|
+
name: "Google Gemini API key",
|
|
18
|
+
type: "env_var",
|
|
19
|
+
vars: [{ name: "GEMINI_API_KEY", secret: true }],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "xai-api-key",
|
|
23
|
+
name: "xAI API key",
|
|
24
|
+
type: "env_var",
|
|
25
|
+
vars: [{ name: "XAI_API_KEY", secret: true }],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "openrouter-api-key",
|
|
29
|
+
name: "OpenRouter API key",
|
|
30
|
+
type: "env_var",
|
|
31
|
+
vars: [{ name: "OPENROUTER_API_KEY", secret: true }],
|
|
32
|
+
},
|
|
33
|
+
{ id: "pi-stored-credentials", name: "pi stored credentials" },
|
|
34
|
+
];
|
|
35
|
+
const IDS = new Set(AUTH_METHODS.map(({ id }) => id));
|
|
36
|
+
export function authenticateMethod(methodId) {
|
|
37
|
+
if (!IDS.has(methodId))
|
|
38
|
+
throw adapterError("unknown_auth_method");
|
|
39
|
+
return {};
|
|
40
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { AgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
4
|
+
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
5
|
+
export declare function thinkingLevelOption(session: AgentSession): SessionConfigOption;
|
|
6
|
+
export declare function applyConfig(session: AgentSession, modelRuntime: ModelRuntime, configId: string, value: string | boolean): Promise<SessionConfigOption[]>;
|
|
7
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGlF,eAAO,MAAM,eAAe,sEAAuE,CAAC;AACpG,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,mBAAmB,CAS9E;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,YAAY,EACrB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,OAAO,GACtB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA4BhC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { adapterError } from "./errors.js";
|
|
2
|
+
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
3
|
+
export function thinkingLevelOption(session) {
|
|
4
|
+
return {
|
|
5
|
+
id: "thinkingLevel",
|
|
6
|
+
name: "Thinking level",
|
|
7
|
+
type: "select",
|
|
8
|
+
category: "thought_level",
|
|
9
|
+
currentValue: session.thinkingLevel,
|
|
10
|
+
options: THINKING_LEVELS.map((value) => ({ value, name: value })),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export async function applyConfig(session, modelRuntime, configId, value) {
|
|
14
|
+
if (configId !== "thinkingLevel" && configId !== "model") {
|
|
15
|
+
throw adapterError("unknown_config_option");
|
|
16
|
+
}
|
|
17
|
+
if (typeof value !== "string")
|
|
18
|
+
throw adapterError("invalid_config_type");
|
|
19
|
+
if (configId === "thinkingLevel") {
|
|
20
|
+
if (!THINKING_LEVELS.includes(value)) {
|
|
21
|
+
throw adapterError("invalid_config_value");
|
|
22
|
+
}
|
|
23
|
+
session.setThinkingLevel(value);
|
|
24
|
+
return [thinkingLevelOption(session)];
|
|
25
|
+
}
|
|
26
|
+
const separator = value.indexOf("/");
|
|
27
|
+
if (separator <= 0 || separator === value.length - 1)
|
|
28
|
+
throw adapterError("invalid_model");
|
|
29
|
+
const provider = value.slice(0, separator);
|
|
30
|
+
const modelId = value.slice(separator + 1);
|
|
31
|
+
const model = modelRuntime.getModel(provider, modelId);
|
|
32
|
+
if (!model)
|
|
33
|
+
throw adapterError("invalid_model");
|
|
34
|
+
if (!modelRuntime.hasConfiguredAuth(model.provider))
|
|
35
|
+
throw adapterError("auth_error");
|
|
36
|
+
try {
|
|
37
|
+
await session.setModel(model);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof Error && /^no api key for /i.test(error.message)) {
|
|
41
|
+
throw adapterError("auth_error");
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
return [thinkingLevelOption(session)];
|
|
46
|
+
}
|
package/dist/deps.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ModelRuntime, SessionManager, type CreateAgentSessionOptions, type CreateAgentSessionResult, type NewSessionOptions, type SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { McpServerStdio } from "@agentclientprotocol/sdk";
|
|
3
|
+
import { type McpClientHandle } from "./mcp-bridge.js";
|
|
4
|
+
export interface PiAcpDeps {
|
|
5
|
+
createAgentSession(opts: CreateAgentSessionOptions): Promise<CreateAgentSessionResult>;
|
|
6
|
+
sessions: {
|
|
7
|
+
create(cwd: string, sessionDir?: string, options?: NewSessionOptions): SessionManager;
|
|
8
|
+
open(path: string, sessionDir?: string, cwdOverride?: string): SessionManager;
|
|
9
|
+
forkFrom(sourcePath: string, targetCwd: string, sessionDir?: string, options?: NewSessionOptions): SessionManager;
|
|
10
|
+
list(cwd: string, sessionDir?: string): Promise<SessionInfo[]>;
|
|
11
|
+
listAll(sessionDir?: string): Promise<SessionInfo[]>;
|
|
12
|
+
};
|
|
13
|
+
modelRuntime: ModelRuntime;
|
|
14
|
+
sessionDir?: string;
|
|
15
|
+
connectMcpClient(server: McpServerStdio, signal: AbortSignal): Promise<McpClientHandle>;
|
|
16
|
+
sleep(ms: number, signal: AbortSignal): Promise<void>;
|
|
17
|
+
graceMs: number;
|
|
18
|
+
mcpTimeoutMs: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function realSleep(ms: number, signal: AbortSignal): Promise<void>;
|
|
21
|
+
export declare function resolveDeps(partial?: Partial<PiAcpDeps>): Promise<PiAcpDeps>;
|
|
22
|
+
//# sourceMappingURL=deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../src/deps.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,cAAc,EAEd,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhF,MAAM,WAAW,SAAS;IACxB,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACvF,QAAQ,EAAE;QACR,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAAC;QACtF,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC;QAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAAC;QAClH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;KACtD,CAAC;IACF,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACxF,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxE;AAED,wBAAsB,WAAW,CAAC,OAAO,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAuBtF"}
|
package/dist/deps.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ModelRuntime, SessionManager, createAgentSession, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
|
+
import { connectDefaultMcpClient } from "./mcp-bridge.js";
|
|
4
|
+
export function realSleep(ms, signal) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
if (signal.aborted) {
|
|
7
|
+
reject(signal.reason);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const timer = setTimeout(resolve, ms);
|
|
11
|
+
signal.addEventListener("abort", () => {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
reject(signal.reason);
|
|
14
|
+
}, { once: true });
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function resolveDeps(partial = {}) {
|
|
18
|
+
const sleep = partial.sleep ?? realSleep;
|
|
19
|
+
const mcpTimeoutMs = partial.mcpTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
|
|
20
|
+
const modelRuntime = partial.modelRuntime ?? await ModelRuntime.create();
|
|
21
|
+
const sessions = partial.sessions ?? {
|
|
22
|
+
create: SessionManager.create,
|
|
23
|
+
open: SessionManager.open,
|
|
24
|
+
forkFrom: SessionManager.forkFrom,
|
|
25
|
+
list: SessionManager.list,
|
|
26
|
+
listAll: (sessionDir) => SessionManager.listAll(sessionDir),
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
createAgentSession: partial.createAgentSession ?? createAgentSession,
|
|
30
|
+
sessions,
|
|
31
|
+
modelRuntime,
|
|
32
|
+
sessionDir: partial.sessionDir,
|
|
33
|
+
sleep,
|
|
34
|
+
graceMs: partial.graceMs ?? 5_000,
|
|
35
|
+
mcpTimeoutMs,
|
|
36
|
+
connectMcpClient: partial.connectMcpClient ??
|
|
37
|
+
((server, signal) => connectDefaultMcpClient(server, signal, mcpTimeoutMs, sleep)),
|
|
38
|
+
};
|
|
39
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RequestError } from "@agentclientprotocol/sdk";
|
|
2
|
+
export type ErrorKind = "auth_error" | "rate_limit" | "billing_error" | "provider_error" | "invalid_model" | "empty_prompt" | "session_busy" | "invalid_config_value" | "invalid_config_type" | "unknown_config_option" | "invalid_cwd" | "unknown_session" | "session_already_open" | "session_terminated" | "session_corrupt" | "session_not_forkable" | "mcp_init_error" | "unsupported_mcp_transport" | "structured_tool_collision" | "invalid_output_schema" | "invalid_cursor" | "unknown_auth_method" | "notification_error" | "internal_error";
|
|
3
|
+
export interface DiagnosticLike {
|
|
4
|
+
type: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
error?: {
|
|
7
|
+
name?: string;
|
|
8
|
+
message: string;
|
|
9
|
+
stack?: string;
|
|
10
|
+
code?: string | number;
|
|
11
|
+
};
|
|
12
|
+
details?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface TerminalAssistantLike {
|
|
15
|
+
stopReason: string;
|
|
16
|
+
errorMessage?: string;
|
|
17
|
+
diagnostics?: DiagnosticLike[];
|
|
18
|
+
}
|
|
19
|
+
export declare function redactedDiagnostics(diagnostics: readonly DiagnosticLike[] | undefined): {
|
|
20
|
+
type: string;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
}[] | undefined;
|
|
23
|
+
export declare function adapterError(kind: ErrorKind, extras?: {
|
|
24
|
+
server?: string;
|
|
25
|
+
details?: Array<{
|
|
26
|
+
type: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}>;
|
|
29
|
+
}): RequestError;
|
|
30
|
+
export declare function classifyPreflight(error: unknown): RequestError;
|
|
31
|
+
export declare function classifyTerminal(message: TerminalAssistantLike): RequestError;
|
|
32
|
+
export declare function unexpectedError(error: unknown, terminal?: TerminalAssistantLike): RequestError;
|
|
33
|
+
export declare function isRequestError(error: unknown): error is RequestError;
|
|
34
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,MAAM,MAAM,SAAS,GACjB,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,eAAe,GACf,cAAc,GACd,cAAc,GACd,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,GACvB,aAAa,GACb,iBAAiB,GACjB,sBAAsB,GACtB,oBAAoB,GACpB,iBAAiB,GACjB,sBAAsB,GACtB,gBAAgB,GAChB,2BAA2B,GAC3B,2BAA2B,GAC3B,uBAAuB,GACvB,gBAAgB,GAChB,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,CAAC;AA+CrB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,GAAG,SAAS;;;gBAIrF;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,EACf,MAAM,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAO,GACrF,YAAY,CAOd;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAW9D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,YAAY,CAsB7E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,qBAAqB,GAAG,YAAY,CAK9F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE"}
|