@agentknight/sdk 0.9.35
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 +201 -0
- package/dist/chunks/acp-adapter-4N2QCTAL.js +936 -0
- package/dist/chunks/chunk-AHJSR7OY.js +24 -0
- package/dist/chunks/chunk-ATMWDEPF.js +1017 -0
- package/dist/chunks/chunk-C7XDS2KG.js +24 -0
- package/dist/chunks/chunk-JFHUAIRY.js +2659 -0
- package/dist/chunks/chunk-MVUOCZFL.js +47 -0
- package/dist/chunks/chunk-SJHJER3B.js +340 -0
- package/dist/chunks/chunk-W3PG2W3C.js +139 -0
- package/dist/chunks/chunk-ZKAN2FO7.js +2654 -0
- package/dist/chunks/copilot-adapter-CLIDIK2S.js +5179 -0
- package/dist/chunks/managed-agent-adapter-HP5VE3AH.js +1252 -0
- package/dist/chunks/pi-adapter-GSDVA7EG.js +1177 -0
- package/dist/index.d.ts +1962 -0
- package/dist/index.js +9187 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +13 -0
- package/package.json +65 -0
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
latestPreviewItems
|
|
3
|
+
} from "./chunk-MVUOCZFL.js";
|
|
4
|
+
import {
|
|
5
|
+
AgentSessionRenameError,
|
|
6
|
+
UnsupportedAgentOperationError,
|
|
7
|
+
validateSessionName
|
|
8
|
+
} from "./chunk-SJHJER3B.js";
|
|
9
|
+
import {
|
|
10
|
+
InteractionRequestSchema,
|
|
11
|
+
REASONING_EFFORTS,
|
|
12
|
+
SESSION_TIMELINE_PAGE_SIZE
|
|
13
|
+
} from "./chunk-ZKAN2FO7.js";
|
|
14
|
+
|
|
15
|
+
// src/pi-adapter.ts
|
|
16
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
17
|
+
import { open, readdir, realpath, stat } from "node:fs/promises";
|
|
18
|
+
import { dirname, join, resolve } from "node:path";
|
|
19
|
+
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
20
|
+
import {
|
|
21
|
+
DefaultResourceLoader,
|
|
22
|
+
ModelRuntime,
|
|
23
|
+
ProjectTrustStore,
|
|
24
|
+
SessionManager,
|
|
25
|
+
SettingsManager,
|
|
26
|
+
VERSION,
|
|
27
|
+
createAgentSession,
|
|
28
|
+
getAgentDir,
|
|
29
|
+
hasTrustRequiringProjectResources
|
|
30
|
+
} from "@earendil-works/pi-coding-agent";
|
|
31
|
+
|
|
32
|
+
// src/pi-timeline.ts
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
var FINAL_REVISION = 1e9;
|
|
35
|
+
var PI_PROMPT_SOURCE_ENTRY = "agentknight.prompt-source.v1";
|
|
36
|
+
function key(value) {
|
|
37
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 32);
|
|
38
|
+
}
|
|
39
|
+
var PiTimeline = class {
|
|
40
|
+
constructor(sessionId, entries = []) {
|
|
41
|
+
this.sessionId = sessionId;
|
|
42
|
+
this.#turnId = `pi:${sessionId}`;
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (entry.type !== "custom" || entry.customType !== PI_PROMPT_SOURCE_ENTRY) continue;
|
|
45
|
+
const data = entry.data;
|
|
46
|
+
if (typeof data !== "object" || data === null || !("projectionId" in data) || typeof data.projectionId !== "string" || !("messageId" in data) || typeof data.messageId !== "string" || data.messageId.length === 0 || data.messageId.length > 256) continue;
|
|
47
|
+
this.#promptSources.set(data.projectionId, data.messageId);
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.type === "message") {
|
|
51
|
+
const id = this.#nextMessageId(entry.message);
|
|
52
|
+
this.#message(entry.message, id, true);
|
|
53
|
+
} else if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
54
|
+
this.notice(`pi:${entry.id}`, entry.timestamp, "info", "Summary", entry.summary);
|
|
55
|
+
} else if (entry.type === "custom_message" && entry.display) {
|
|
56
|
+
const id = `pi:${entry.id}`;
|
|
57
|
+
const content = this.#text(entry.content, id, entry.timestamp);
|
|
58
|
+
this.notice(id, entry.timestamp, "info", entry.customType, content);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
sessionId;
|
|
63
|
+
items = /* @__PURE__ */ new Map();
|
|
64
|
+
#counts = /* @__PURE__ */ new Map();
|
|
65
|
+
#activeMessages = /* @__PURE__ */ new Map();
|
|
66
|
+
#promptSources = /* @__PURE__ */ new Map();
|
|
67
|
+
#turnId;
|
|
68
|
+
event(event, acknowledgedMessageId) {
|
|
69
|
+
const previous = new Map(this.items);
|
|
70
|
+
if (event.type === "message_start" || event.type === "message_update" || event.type === "message_end") {
|
|
71
|
+
const signature = this.#signature(event.message);
|
|
72
|
+
let id = this.#activeMessages.get(signature);
|
|
73
|
+
if (!id) {
|
|
74
|
+
id = this.#nextMessageId(event.message);
|
|
75
|
+
this.#activeMessages.set(signature, id);
|
|
76
|
+
}
|
|
77
|
+
if (event.message.role === "user" && acknowledgedMessageId) this.#promptSources.set(id, acknowledgedMessageId);
|
|
78
|
+
this.#message(event.message, id, event.type === "message_end");
|
|
79
|
+
if (event.type === "message_end") this.#activeMessages.delete(signature);
|
|
80
|
+
} else if (event.type === "tool_execution_start" || event.type === "tool_execution_update" || event.type === "tool_execution_end") {
|
|
81
|
+
this.#tool(
|
|
82
|
+
event.toolCallId,
|
|
83
|
+
event.toolName,
|
|
84
|
+
event.type === "tool_execution_end" ? event.isError ? "failed" : "completed" : "running"
|
|
85
|
+
);
|
|
86
|
+
if (event.type === "tool_execution_end") {
|
|
87
|
+
this.#text(event.result.content, this.#toolId(event.toolCallId), this.items.get(this.#toolId(event.toolCallId)).timestamp);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return [...this.items.values()].filter((item) => previous.get(item.id) !== item);
|
|
91
|
+
}
|
|
92
|
+
notice(id, timestamp, severity, title, content) {
|
|
93
|
+
return this.#put({
|
|
94
|
+
id,
|
|
95
|
+
revision: FINAL_REVISION,
|
|
96
|
+
turnId: this.#turnId,
|
|
97
|
+
timestamp,
|
|
98
|
+
sourceEventIds: [id],
|
|
99
|
+
kind: "notice",
|
|
100
|
+
actor: "system",
|
|
101
|
+
severity,
|
|
102
|
+
title: title.trim().slice(0, 256) || "PI",
|
|
103
|
+
content: content.slice(0, 4096)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
#signature(message) {
|
|
107
|
+
return `${message.role}:${message.timestamp}`;
|
|
108
|
+
}
|
|
109
|
+
#nextMessageId(message) {
|
|
110
|
+
const signature = this.#signature(message);
|
|
111
|
+
const index = this.#counts.get(signature) ?? 0;
|
|
112
|
+
this.#counts.set(signature, index + 1);
|
|
113
|
+
return `pi:${key(`${this.sessionId}:${signature}:${index}`)}`;
|
|
114
|
+
}
|
|
115
|
+
#message(message, id, final) {
|
|
116
|
+
const timestamp = new Date(message.timestamp).toISOString();
|
|
117
|
+
if (message.role === "user") this.#turnId = id;
|
|
118
|
+
if (message.role === "user" || message.role === "assistant") {
|
|
119
|
+
const content = this.#text(message.content, id, timestamp);
|
|
120
|
+
const limit = message.role === "user" ? 1e5 : 5e5;
|
|
121
|
+
if (content.length > limit) {
|
|
122
|
+
this.notice(`${id}:truncated`, timestamp, "warning", "Content truncated", "The PI message exceeds the timeline content limit.");
|
|
123
|
+
}
|
|
124
|
+
const base = {
|
|
125
|
+
id,
|
|
126
|
+
revision: final ? FINAL_REVISION : 1,
|
|
127
|
+
turnId: this.#turnId,
|
|
128
|
+
timestamp,
|
|
129
|
+
sourceEventIds: [id],
|
|
130
|
+
sourceMessageIds: [id, ...this.#promptSources.has(id) ? [this.#promptSources.get(id)] : []],
|
|
131
|
+
content: content.slice(0, limit)
|
|
132
|
+
};
|
|
133
|
+
if (message.role === "user") {
|
|
134
|
+
this.#put({ ...base, kind: "human-message", actor: "human", attachments: [] });
|
|
135
|
+
} else {
|
|
136
|
+
this.#put({
|
|
137
|
+
...base,
|
|
138
|
+
kind: "assistant-message",
|
|
139
|
+
actor: "assistant",
|
|
140
|
+
final,
|
|
141
|
+
...message.model ? { model: message.model.slice(0, 256) } : {}
|
|
142
|
+
});
|
|
143
|
+
for (const part of message.content) {
|
|
144
|
+
if (part.type === "toolCall") this.#tool(part.id, part.name, "running", timestamp);
|
|
145
|
+
}
|
|
146
|
+
if (final && message.stopReason === "error") {
|
|
147
|
+
this.notice(`${id}:error`, timestamp, "error", "PI request failed", message.errorMessage ?? "The model returned an error.");
|
|
148
|
+
} else if (final && message.stopReason === "aborted") {
|
|
149
|
+
this.notice(`${id}:aborted`, timestamp, "warning", "Aborted", message.errorMessage ?? "The request was aborted.");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else if (message.role === "toolResult") {
|
|
153
|
+
this.#tool(message.toolCallId, message.toolName, message.isError ? "failed" : "completed", timestamp);
|
|
154
|
+
this.#text(message.content, this.#toolId(message.toolCallId), timestamp);
|
|
155
|
+
} else if (message.role === "bashExecution") {
|
|
156
|
+
this.notice(id, timestamp, message.exitCode === 0 ? "info" : "error", message.command, message.output);
|
|
157
|
+
} else if (message.role === "custom") {
|
|
158
|
+
if (message.display) this.notice(id, timestamp, "info", message.customType, this.#text(message.content, id, timestamp));
|
|
159
|
+
} else if (message.role === "compactionSummary" || message.role === "branchSummary") {
|
|
160
|
+
this.notice(id, timestamp, "info", "Summary", message.summary);
|
|
161
|
+
} else {
|
|
162
|
+
this.notice(`${id}:unsupported`, timestamp, "warning", "Unsupported PI message", "This native message type cannot be displayed.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
#text(content, id, timestamp) {
|
|
166
|
+
if (typeof content === "string") return content;
|
|
167
|
+
if (content.some((part) => part.type !== "text" && part.type !== "thinking" && part.type !== "toolCall")) {
|
|
168
|
+
this.notice(`${id}:unsupported`, timestamp, "warning", "Unsupported PI content", "This message contains media that cannot be displayed by this adapter.");
|
|
169
|
+
}
|
|
170
|
+
return content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
171
|
+
}
|
|
172
|
+
#toolId(toolCallId) {
|
|
173
|
+
return `pi:tool:${key(`${this.sessionId}:${toolCallId}`)}`;
|
|
174
|
+
}
|
|
175
|
+
#tool(toolCallId, name, status, timestamp) {
|
|
176
|
+
const id = this.#toolId(toolCallId);
|
|
177
|
+
const existing = this.items.get(id);
|
|
178
|
+
if (existing?.kind === "activity-group" && existing.status !== "running" && status === "running") return;
|
|
179
|
+
const startedAt = existing?.timestamp ?? timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
180
|
+
this.#put({
|
|
181
|
+
id,
|
|
182
|
+
revision: status === "running" ? 1 : FINAL_REVISION,
|
|
183
|
+
turnId: existing?.turnId ?? this.#turnId,
|
|
184
|
+
timestamp: startedAt,
|
|
185
|
+
sourceEventIds: [id],
|
|
186
|
+
kind: "activity-group",
|
|
187
|
+
actor: "assistant",
|
|
188
|
+
status,
|
|
189
|
+
steps: [{ id, kind: "tool", name: name.trim().slice(0, 256) || "tool", status, timestamp: startedAt }]
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
#put(item) {
|
|
193
|
+
const previous = this.items.get(item.id);
|
|
194
|
+
if (previous) {
|
|
195
|
+
if (JSON.stringify({ ...previous, revision: 0 }) === JSON.stringify({ ...item, revision: 0 })) return previous;
|
|
196
|
+
item.revision = Math.max(item.revision, previous.revision + 1);
|
|
197
|
+
}
|
|
198
|
+
this.items.set(item.id, item);
|
|
199
|
+
return item;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/pi-adapter.ts
|
|
204
|
+
var PiOpeningCancelledError = class extends Error {
|
|
205
|
+
constructor() {
|
|
206
|
+
super("PI project trust resolution was cancelled or unavailable.");
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var isMissing = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
210
|
+
var modelId = (model) => `${model.provider}/${model.id}`;
|
|
211
|
+
var PiCodingAgentAdapter = class {
|
|
212
|
+
agentId = "pi";
|
|
213
|
+
sdkVersion = VERSION;
|
|
214
|
+
agentModes = ["interactive"];
|
|
215
|
+
supportsReasoningEffort = true;
|
|
216
|
+
supportsSessionListUpdates = true;
|
|
217
|
+
supportsAssistantPreview = true;
|
|
218
|
+
supportsSessionDelete = false;
|
|
219
|
+
supportsFileAttachments = false;
|
|
220
|
+
#agentDirectory;
|
|
221
|
+
#timeout;
|
|
222
|
+
#dependencies;
|
|
223
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
224
|
+
#runtimes = /* @__PURE__ */ new Map();
|
|
225
|
+
#prepared = /* @__PURE__ */ new Map();
|
|
226
|
+
#startingPrompts = /* @__PURE__ */ new Map();
|
|
227
|
+
#openingNotices = /* @__PURE__ */ new Map();
|
|
228
|
+
#opening = /* @__PURE__ */ new Map();
|
|
229
|
+
#cancelledOpenings = /* @__PURE__ */ new Set();
|
|
230
|
+
#pending = /* @__PURE__ */ new Map();
|
|
231
|
+
#modelRuntime;
|
|
232
|
+
#modelRefresh;
|
|
233
|
+
#callbacks;
|
|
234
|
+
#started = false;
|
|
235
|
+
#stopping = false;
|
|
236
|
+
constructor(options = {}, dependencies = {}) {
|
|
237
|
+
this.#agentDirectory = resolve(options.agentDirectory ?? getAgentDir());
|
|
238
|
+
this.#timeout = options.interactionTimeoutMs ?? 12e4;
|
|
239
|
+
if (!Number.isFinite(this.#timeout) || this.#timeout <= 0) throw new Error("PI interaction timeout must be positive.");
|
|
240
|
+
this.#dependencies = dependencies;
|
|
241
|
+
}
|
|
242
|
+
async start() {
|
|
243
|
+
if (this.#started) return;
|
|
244
|
+
this.#stopping = false;
|
|
245
|
+
this.#modelRuntime = this.#dependencies.modelRuntime ?? await ModelRuntime.create({
|
|
246
|
+
authPath: join(this.#agentDirectory, "auth.json"),
|
|
247
|
+
modelsPath: join(this.#agentDirectory, "models.json"),
|
|
248
|
+
modelsStorePath: join(this.#agentDirectory, "cache", "models.json"),
|
|
249
|
+
allowModelNetwork: true,
|
|
250
|
+
modelRefreshTimeoutMs: 15e3
|
|
251
|
+
});
|
|
252
|
+
await this.#modelRuntime.getAvailable();
|
|
253
|
+
this.#checkRegistry();
|
|
254
|
+
await this.#refreshSessions();
|
|
255
|
+
if (this.#stopping) throw new Error("PI adapter stopped during startup.");
|
|
256
|
+
this.#started = true;
|
|
257
|
+
}
|
|
258
|
+
async stop() {
|
|
259
|
+
this.#stopping = true;
|
|
260
|
+
this.#started = false;
|
|
261
|
+
const modelRefresh = this.#modelRefresh;
|
|
262
|
+
modelRefresh?.controller.abort(new Error("PI adapter stopped during model refresh."));
|
|
263
|
+
for (const startup of this.#startingPrompts.values()) {
|
|
264
|
+
startup.cancelled = true;
|
|
265
|
+
startup.queue = [];
|
|
266
|
+
}
|
|
267
|
+
this.#dismissPending();
|
|
268
|
+
if (modelRefresh) await Promise.allSettled([modelRefresh.work]);
|
|
269
|
+
const errors = [];
|
|
270
|
+
const opening = await Promise.allSettled([
|
|
271
|
+
...this.#opening.values(),
|
|
272
|
+
...[...this.#startingPrompts.values()].map((startup) => startup.work)
|
|
273
|
+
]);
|
|
274
|
+
for (const result of opening) {
|
|
275
|
+
if (result.status === "rejected" && !(result.reason instanceof PiOpeningCancelledError)) errors.push(result.reason);
|
|
276
|
+
}
|
|
277
|
+
for (const [id, runtime] of this.#runtimes) {
|
|
278
|
+
try {
|
|
279
|
+
await this.#abort(runtime);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
errors.push(error);
|
|
282
|
+
} finally {
|
|
283
|
+
runtime.unsubscribe();
|
|
284
|
+
runtime.session.dispose();
|
|
285
|
+
this.#runtimes.delete(id);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
this.#sessions.clear();
|
|
289
|
+
this.#prepared.clear();
|
|
290
|
+
this.#openingNotices.clear();
|
|
291
|
+
this.#modelRuntime = void 0;
|
|
292
|
+
if (errors.length) throw new AggregateError(errors, "PI shutdown failed.");
|
|
293
|
+
}
|
|
294
|
+
async canUpgrade() {
|
|
295
|
+
return this.#started && !this.#stopping && this.#opening.size === 0 && this.#startingPrompts.size === 0 && this.#pending.size === 0 && this.#modelRefresh === void 0 && [...this.#runtimes.values()].every((runtime) => !runtime.busy && !runtime.aborting && runtime.queue.length === 0 && runtime.primarySource === void 0 && runtime.steeringSources.length === 0 && runtime.followUpSources.length === 0 && runtime.session.isIdle === true && runtime.session.isStreaming === false && runtime.session.isCompacting === false && runtime.session.isRetrying === false && runtime.session.isBashRunning === false && runtime.session.hasPendingBashMessages === false && runtime.session.pendingMessageCount === 0);
|
|
296
|
+
}
|
|
297
|
+
async listModels() {
|
|
298
|
+
this.#assertStarted();
|
|
299
|
+
if (this.#modelRefresh) return this.#modelRefresh.work;
|
|
300
|
+
const controller = new AbortController();
|
|
301
|
+
const timeout = setTimeout(() => controller.abort(new Error("PI model refresh timed out.")), 15e3);
|
|
302
|
+
timeout.unref();
|
|
303
|
+
const refresh = {
|
|
304
|
+
controller,
|
|
305
|
+
work: this.#listModels(controller.signal)
|
|
306
|
+
};
|
|
307
|
+
this.#modelRefresh = refresh;
|
|
308
|
+
try {
|
|
309
|
+
return await refresh.work;
|
|
310
|
+
} finally {
|
|
311
|
+
clearTimeout(timeout);
|
|
312
|
+
if (this.#modelRefresh === refresh) this.#modelRefresh = void 0;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async #listModels(signal) {
|
|
316
|
+
const registry = this.#modelRuntime;
|
|
317
|
+
if (!registry) throw new Error("PI model runtime is unavailable.");
|
|
318
|
+
const result = await registry.refresh({ allowNetwork: true, force: true, signal });
|
|
319
|
+
if (result.aborted || signal.aborted) {
|
|
320
|
+
throw new Error("PI model refresh was cancelled or timed out.", { cause: signal.reason });
|
|
321
|
+
}
|
|
322
|
+
if (result.errors.size > 0) {
|
|
323
|
+
throw new AggregateError(
|
|
324
|
+
[...result.errors.values()],
|
|
325
|
+
`PI model refresh failed: ${[...result.errors.keys()].join(", ")}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
this.#assertStarted();
|
|
329
|
+
const settings = this.#settings(process.cwd());
|
|
330
|
+
const preferred = `${settings.getDefaultProvider()}/${settings.getDefaultModel()}`;
|
|
331
|
+
const models = await registry.getAvailable(void 0, { signal });
|
|
332
|
+
if (signal.aborted) throw new Error("PI model refresh was cancelled or timed out.", { cause: signal.reason });
|
|
333
|
+
this.#assertStarted();
|
|
334
|
+
this.#checkRegistry();
|
|
335
|
+
const defaultId = models.some((model) => modelId(model) === preferred) ? preferred : modelId(models[0]);
|
|
336
|
+
return models.map((model) => {
|
|
337
|
+
const supportedReasoningEfforts = REASONING_EFFORTS.filter((level) => getSupportedThinkingLevels(model).some((supported) => supported === level));
|
|
338
|
+
const defaultReasoningEffort = this.#thinking(model, "default", settings);
|
|
339
|
+
return {
|
|
340
|
+
id: modelId(model),
|
|
341
|
+
name: model.name.slice(0, 260),
|
|
342
|
+
isDefault: modelId(model) === defaultId,
|
|
343
|
+
supportedReasoningEfforts,
|
|
344
|
+
...supportedReasoningEfforts.some((level) => level === defaultReasoningEffort) ? { defaultReasoningEffort } : {}
|
|
345
|
+
};
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
async listSessions(userId, machineId) {
|
|
349
|
+
this.#assertStarted();
|
|
350
|
+
await this.#refreshSessions();
|
|
351
|
+
return [...this.#sessions.values()].sort((a, b) => b.modified.getTime() - a.modified.getTime()).map((info) => this.#summary(info, userId, machineId));
|
|
352
|
+
}
|
|
353
|
+
async createSession(options) {
|
|
354
|
+
this.#assertStarted();
|
|
355
|
+
this.#validateMode(options.agentMode);
|
|
356
|
+
const model = this.#model(options.modelId);
|
|
357
|
+
const cwd = resolve(options.workingDirectory);
|
|
358
|
+
if (!(await stat(cwd)).isDirectory()) throw new Error(`PI workspace is not a directory: ${cwd}`);
|
|
359
|
+
this.#assertStarted();
|
|
360
|
+
const settings = this.#settings(cwd);
|
|
361
|
+
const thinking = options.reasoningEffort === void 0 || options.reasoningEffort === "default" ? "default" : this.#thinking(model, options.reasoningEffort, settings);
|
|
362
|
+
const sessionDirectory = join(this.#agentDirectory, "sessions", `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`);
|
|
363
|
+
const manager = SessionManager.create(cwd, sessionDirectory);
|
|
364
|
+
const header = manager.getHeader();
|
|
365
|
+
const info = {
|
|
366
|
+
id: manager.getSessionId(),
|
|
367
|
+
path: manager.getSessionFile(),
|
|
368
|
+
cwd,
|
|
369
|
+
created: new Date(header.timestamp),
|
|
370
|
+
modified: new Date(header.timestamp),
|
|
371
|
+
messageCount: 0,
|
|
372
|
+
firstMessage: "",
|
|
373
|
+
allMessagesText: ""
|
|
374
|
+
};
|
|
375
|
+
this.#sessions.set(info.id, info);
|
|
376
|
+
this.#prepared.set(info.id, { manager, modelId: modelId(model), thinking });
|
|
377
|
+
this.#callbacks?.onSessionInventoryChanged(info.id);
|
|
378
|
+
return this.#summary(info, options.userId, options.machineId);
|
|
379
|
+
}
|
|
380
|
+
async getSessionConfiguration(sessionId) {
|
|
381
|
+
this.#assertStarted();
|
|
382
|
+
const runtime = this.#runtimes.get(sessionId);
|
|
383
|
+
if (runtime) return this.#configuration(runtime);
|
|
384
|
+
const manager = await this.#openKnownSession(sessionId);
|
|
385
|
+
const context = manager.buildSessionContext();
|
|
386
|
+
const prepared = this.#prepared.get(sessionId);
|
|
387
|
+
if (prepared) {
|
|
388
|
+
return {
|
|
389
|
+
modelId: prepared.modelId,
|
|
390
|
+
agentMode: "interactive",
|
|
391
|
+
reasoningEffort: prepared.thinking === "default" ? this.#thinking(this.#model(prepared.modelId), "default", this.#settings(manager.getCwd())) : prepared.thinking,
|
|
392
|
+
configOptions: this.#configOptions(manager.getCwd())
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
...context.model ? { modelId: `${context.model.provider}/${context.model.modelId}` } : {},
|
|
397
|
+
agentMode: "interactive",
|
|
398
|
+
reasoningEffort: context.thinkingLevel,
|
|
399
|
+
configOptions: this.#configOptions(manager.getCwd())
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async renameSession(sessionId, name, assertAccess) {
|
|
403
|
+
const requested = validateSessionName(name);
|
|
404
|
+
this.#assertStarted();
|
|
405
|
+
assertAccess?.();
|
|
406
|
+
let runtime = this.#runtimes.get(sessionId);
|
|
407
|
+
const opening = this.#opening.get(sessionId);
|
|
408
|
+
if (!runtime && opening) runtime = await opening;
|
|
409
|
+
const manager = runtime?.session.sessionManager ?? await this.#openKnownSession(sessionId);
|
|
410
|
+
const pending = this.#opening.get(sessionId);
|
|
411
|
+
runtime = pending ? await pending : this.#runtimes.get(sessionId);
|
|
412
|
+
this.#assertStarted();
|
|
413
|
+
assertAccess?.();
|
|
414
|
+
let confirmed;
|
|
415
|
+
if (runtime) {
|
|
416
|
+
runtime.session.setSessionName(requested);
|
|
417
|
+
confirmed = runtime.session.sessionName;
|
|
418
|
+
} else {
|
|
419
|
+
manager.appendSessionInfo(requested);
|
|
420
|
+
confirmed = manager.getSessionName();
|
|
421
|
+
}
|
|
422
|
+
this.#assertStarted();
|
|
423
|
+
assertAccess?.();
|
|
424
|
+
if (confirmed !== requested) {
|
|
425
|
+
throw new AgentSessionRenameError("SESSION_RENAME_FAILED", "PI did not confirm the requested session name.");
|
|
426
|
+
}
|
|
427
|
+
const info = this.#sessions.get(sessionId);
|
|
428
|
+
if (info) info.name = confirmed;
|
|
429
|
+
this.#callbacks?.onSessionInventoryChanged(sessionId);
|
|
430
|
+
return confirmed;
|
|
431
|
+
}
|
|
432
|
+
async configureSession(sessionId, options) {
|
|
433
|
+
this.#validateMode(options.agentMode);
|
|
434
|
+
const option = options.configOption;
|
|
435
|
+
if (option && (options.modelId !== void 0 || options.agentMode !== void 0 || options.reasoningEffort !== void 0)) {
|
|
436
|
+
throw new UnsupportedAgentOperationError("pi", "combined native setting and session configuration changes");
|
|
437
|
+
}
|
|
438
|
+
if (option && (option.id !== "defaultProjectTrust" || !["ask", "always", "never"].includes(option.value))) {
|
|
439
|
+
throw new UnsupportedAgentOperationError("pi", `configuration ${option.id}=${option.value}`);
|
|
440
|
+
}
|
|
441
|
+
const current = await this.getSessionConfiguration(sessionId);
|
|
442
|
+
if (option) {
|
|
443
|
+
const settings = this.#settings(process.cwd());
|
|
444
|
+
const value = option.value;
|
|
445
|
+
if (value !== "ask" && value !== "always" && value !== "never") {
|
|
446
|
+
throw new UnsupportedAgentOperationError("pi", `project trust ${value}`);
|
|
447
|
+
}
|
|
448
|
+
settings.setDefaultProjectTrust(value);
|
|
449
|
+
await settings.flush();
|
|
450
|
+
this.#checkSettings(settings);
|
|
451
|
+
if (this.#settings(process.cwd()).getDefaultProjectTrust() !== value) {
|
|
452
|
+
throw new Error("PI project trust setting could not be saved.");
|
|
453
|
+
}
|
|
454
|
+
for (const id of this.#sessions.keys()) this.#callbacks?.onSessionInventoryChanged(id);
|
|
455
|
+
}
|
|
456
|
+
if ((options.modelId === void 0 || options.modelId === current.modelId) && (options.reasoningEffort === void 0 || options.reasoningEffort === current.reasoningEffort)) {
|
|
457
|
+
return { ...current, configOptions: this.#configOptions(process.cwd()) };
|
|
458
|
+
}
|
|
459
|
+
const runtime = await this.#runtime(sessionId);
|
|
460
|
+
const model = options.modelId === void 0 ? runtime.session.model : this.#model(options.modelId);
|
|
461
|
+
if (!model) throw new Error("No PI model is selected.");
|
|
462
|
+
const thinking = options.reasoningEffort === void 0 ? void 0 : this.#thinking(model, options.reasoningEffort, runtime.session.settingsManager);
|
|
463
|
+
const modelChanged = !runtime.session.model || modelId(model) !== modelId(runtime.session.model);
|
|
464
|
+
const thinkingChanged = thinking !== void 0 && thinking !== runtime.session.thinkingLevel;
|
|
465
|
+
if (!modelChanged && !thinkingChanged) return this.#configuration(runtime);
|
|
466
|
+
if (runtime.busy || runtime.session.isStreaming) throw new Error("PI session configuration cannot change while a prompt is running.");
|
|
467
|
+
if (modelChanged) await runtime.session.setModel(model);
|
|
468
|
+
if (thinking !== void 0 && (modelChanged || thinkingChanged)) runtime.session.setThinkingLevel(thinking);
|
|
469
|
+
this.#callbacks?.onSessionInventoryChanged(sessionId);
|
|
470
|
+
return this.#configuration(runtime);
|
|
471
|
+
}
|
|
472
|
+
async getSessionEnvironment(sessionId) {
|
|
473
|
+
this.#assertStarted();
|
|
474
|
+
const runtime = this.#runtimes.get(sessionId);
|
|
475
|
+
if (!runtime) {
|
|
476
|
+
await this.#openKnownSession(sessionId);
|
|
477
|
+
return { tools: [], slashCommands: [], skills: [] };
|
|
478
|
+
}
|
|
479
|
+
const { session } = runtime;
|
|
480
|
+
const active = new Set(session.getActiveToolNames());
|
|
481
|
+
return {
|
|
482
|
+
tools: session.getAllTools().filter((tool) => active.has(tool.name)).map((tool) => ({
|
|
483
|
+
name: tool.name.slice(0, 260),
|
|
484
|
+
description: tool.description.slice(0, 4096)
|
|
485
|
+
})),
|
|
486
|
+
slashCommands: [],
|
|
487
|
+
skills: []
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
async getSessionRemoteStatus(_sessionId) {
|
|
491
|
+
throw new UnsupportedAgentOperationError("pi", "remote session status");
|
|
492
|
+
}
|
|
493
|
+
async setSessionRemote(_sessionId, _enabled) {
|
|
494
|
+
throw new UnsupportedAgentOperationError("pi", "remote session toggling");
|
|
495
|
+
}
|
|
496
|
+
async readSessionTimelinePage(sessionId, options = {}) {
|
|
497
|
+
this.#assertStarted();
|
|
498
|
+
const runtime = this.#runtimes.get(sessionId);
|
|
499
|
+
const timeline = new PiTimeline(
|
|
500
|
+
sessionId,
|
|
501
|
+
(runtime?.session.sessionManager ?? await this.#openKnownSession(sessionId)).getBranch()
|
|
502
|
+
);
|
|
503
|
+
for (const source of [this.#openingNotices.get(sessionId), runtime?.timeline]) {
|
|
504
|
+
if (!source) continue;
|
|
505
|
+
for (const item of source.items.values()) {
|
|
506
|
+
const persisted = timeline.items.get(item.id);
|
|
507
|
+
if (!persisted || persisted.revision < item.revision) timeline.items.set(item.id, item);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const items = [...timeline.items.values()];
|
|
511
|
+
const pageSize = options.pageSize ?? SESSION_TIMELINE_PAGE_SIZE;
|
|
512
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > SESSION_TIMELINE_PAGE_SIZE) throw new Error("Invalid PI timeline page size.");
|
|
513
|
+
const signature = createHash2("sha256").update(sessionId).update(items.map((item) => item.id).join("\n")).digest("hex").slice(0, 16);
|
|
514
|
+
let end = items.length;
|
|
515
|
+
let cursorStatus = "ok";
|
|
516
|
+
if (options.cursor) {
|
|
517
|
+
const match = /^pi:([a-f0-9]{16}):([0-9]+)$/.exec(options.cursor);
|
|
518
|
+
if (!match || match[1] !== signature || Number(match[2]) > items.length) cursorStatus = "expired";
|
|
519
|
+
else end = Number(match[2]);
|
|
520
|
+
}
|
|
521
|
+
const start = Math.max(0, end - pageSize);
|
|
522
|
+
return {
|
|
523
|
+
items: options.metadataOnly ? options.assistantPreview === true ? latestPreviewItems(items) : [] : items.slice(start, end),
|
|
524
|
+
cursor: `pi:${signature}:${options.metadataOnly ? end : start}`,
|
|
525
|
+
hasMore: options.metadataOnly ? options.assistantPreview !== true && end > 0 : start > 0,
|
|
526
|
+
cursorStatus
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
async watchSession(sessionId, watched) {
|
|
530
|
+
this.#assertStarted();
|
|
531
|
+
if (watched && !this.#runtimes.has(sessionId)) await this.#openKnownSession(sessionId);
|
|
532
|
+
}
|
|
533
|
+
setSessionCallbacks(callbacks) {
|
|
534
|
+
this.#callbacks = callbacks;
|
|
535
|
+
}
|
|
536
|
+
async sendPrompt(sessionId, prompt, mode, attachments, _persistInteractions, fileAttachments) {
|
|
537
|
+
if (attachments?.length) throw new UnsupportedAgentOperationError("pi", "image attachments");
|
|
538
|
+
if (fileAttachments?.length) throw new UnsupportedAgentOperationError("pi", "file attachments");
|
|
539
|
+
if (!prompt.trim()) throw new Error("PI prompt must not be empty.");
|
|
540
|
+
if (mode !== "enqueue" && mode !== "immediate") throw new UnsupportedAgentOperationError("pi", `prompt delivery mode ${mode}`);
|
|
541
|
+
this.#assertStarted();
|
|
542
|
+
let runtime = this.#runtimes.get(sessionId);
|
|
543
|
+
if (!runtime) {
|
|
544
|
+
const configuration = await this.getSessionConfiguration(sessionId);
|
|
545
|
+
this.#assertStarted();
|
|
546
|
+
if (!configuration.modelId) throw new Error("No PI model is selected.");
|
|
547
|
+
this.#model(configuration.modelId);
|
|
548
|
+
runtime = this.#runtimes.get(sessionId);
|
|
549
|
+
}
|
|
550
|
+
if (this.#cancelledOpenings.has(sessionId)) throw new Error("PI session is being aborted.");
|
|
551
|
+
const message = { id: randomUUID(), text: prompt, mode };
|
|
552
|
+
const startup = this.#startingPrompts.get(sessionId);
|
|
553
|
+
if (startup) {
|
|
554
|
+
if (startup.cancelled) throw new Error("PI session is being aborted.");
|
|
555
|
+
startup.queue.push(message);
|
|
556
|
+
return { messageId: message.id, deliveryState: "queued" };
|
|
557
|
+
}
|
|
558
|
+
if (!runtime) {
|
|
559
|
+
this.#startPrompts(sessionId, message);
|
|
560
|
+
return { messageId: message.id, deliveryState: "processing" };
|
|
561
|
+
}
|
|
562
|
+
if (runtime.aborting) throw new Error("PI session is being aborted.");
|
|
563
|
+
if (!runtime.session.model) throw new Error("No PI model is selected.");
|
|
564
|
+
this.#model(modelId(runtime.session.model));
|
|
565
|
+
const queued = runtime.busy || runtime.session.isStreaming;
|
|
566
|
+
runtime.queue.push(message);
|
|
567
|
+
if (queued) this.#dispatchQueued(runtime);
|
|
568
|
+
else this.#runNext(runtime);
|
|
569
|
+
return { messageId: message.id, deliveryState: queued ? "queued" : "processing" };
|
|
570
|
+
}
|
|
571
|
+
async abortSession(sessionId) {
|
|
572
|
+
this.#assertStarted();
|
|
573
|
+
const startup = this.#startingPrompts.get(sessionId);
|
|
574
|
+
const opening = this.#opening.get(sessionId);
|
|
575
|
+
if (startup || opening) {
|
|
576
|
+
if (startup) {
|
|
577
|
+
startup.cancelled = true;
|
|
578
|
+
startup.queue = [];
|
|
579
|
+
}
|
|
580
|
+
this.#cancelledOpenings.add(sessionId);
|
|
581
|
+
this.#dismissPending(sessionId);
|
|
582
|
+
try {
|
|
583
|
+
await opening;
|
|
584
|
+
} catch (error) {
|
|
585
|
+
if (!(error instanceof PiOpeningCancelledError)) throw error;
|
|
586
|
+
} finally {
|
|
587
|
+
try {
|
|
588
|
+
await startup?.work;
|
|
589
|
+
} finally {
|
|
590
|
+
this.#cancelledOpenings.delete(sessionId);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const runtime = this.#runtimes.get(sessionId);
|
|
595
|
+
if (runtime) await this.#abort(runtime);
|
|
596
|
+
else await this.#openKnownSession(sessionId);
|
|
597
|
+
}
|
|
598
|
+
respondPermission(_permissionRequestId, _decision, _feedback) {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
async respondInteraction(interactionRequestId, response) {
|
|
602
|
+
const pending = this.#pending.get(interactionRequestId);
|
|
603
|
+
if (!pending || pending.request.kind !== response.kind) return false;
|
|
604
|
+
if (pending.request.kind === "user_input" && response.kind === "user_input") {
|
|
605
|
+
if (!pending.request.allowFreeform && (response.wasFreeform || !pending.request.choices.includes(response.answer))) {
|
|
606
|
+
throw new Error("PI interaction response is not one of the offered choices.");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
pending.finish(response);
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
replayPendingInteractions() {
|
|
613
|
+
for (const [id, pending] of this.#pending) this.#publishInteraction(id, pending);
|
|
614
|
+
}
|
|
615
|
+
#assertStarted() {
|
|
616
|
+
if (!this.#started || this.#stopping) throw new Error("PI adapter is not running.");
|
|
617
|
+
}
|
|
618
|
+
#checkRegistry() {
|
|
619
|
+
const registry = this.#modelRuntime;
|
|
620
|
+
if (!registry) throw new Error("PI adapter has not initialized its model registry.");
|
|
621
|
+
if (registry.getError()) throw new Error(`Invalid PI model configuration: ${registry.getError()}`);
|
|
622
|
+
if (registry.getAvailableSnapshot().length === 0) {
|
|
623
|
+
throw new Error("No authenticated PI models are available. Configure PI provider credentials with the PI CLI or provider environment variables.");
|
|
624
|
+
}
|
|
625
|
+
return registry;
|
|
626
|
+
}
|
|
627
|
+
#model(id) {
|
|
628
|
+
const registry = this.#checkRegistry();
|
|
629
|
+
const model = registry.getModels().find((candidate) => modelId(candidate) === id);
|
|
630
|
+
if (!model) throw new Error(`Unknown PI model: ${id}`);
|
|
631
|
+
if (!registry.getAvailableSnapshot().some((candidate) => modelId(candidate) === id)) throw new Error(`Authentication is missing or the PI model is unavailable: ${id}`);
|
|
632
|
+
return model;
|
|
633
|
+
}
|
|
634
|
+
#settings(cwd) {
|
|
635
|
+
const disk = SettingsManager.create(cwd, this.#agentDirectory, { projectTrusted: false });
|
|
636
|
+
this.#checkSettings(disk);
|
|
637
|
+
return disk;
|
|
638
|
+
}
|
|
639
|
+
#checkSettings(settings) {
|
|
640
|
+
const errors = settings.drainErrors();
|
|
641
|
+
if (errors.length) throw new AggregateError(errors.map((item) => item.error), "Unable to read or save PI settings.");
|
|
642
|
+
}
|
|
643
|
+
#configOptions(cwd) {
|
|
644
|
+
return [{
|
|
645
|
+
id: "defaultProjectTrust",
|
|
646
|
+
name: "Project trust",
|
|
647
|
+
value: this.#settings(cwd).getDefaultProjectTrust(),
|
|
648
|
+
options: ["ask", "always", "never"].map((value) => ({ value, name: value }))
|
|
649
|
+
}];
|
|
650
|
+
}
|
|
651
|
+
#thinking(model, effort, settings) {
|
|
652
|
+
const supported = getSupportedThinkingLevels(model);
|
|
653
|
+
if (effort !== "default") {
|
|
654
|
+
if (!supported.some((level) => level === effort)) throw new UnsupportedAgentOperationError("pi", `reasoning effort ${effort} for ${modelId(model)}`);
|
|
655
|
+
return effort;
|
|
656
|
+
}
|
|
657
|
+
const preferred = settings.getDefaultThinkingLevel() ?? "medium";
|
|
658
|
+
return supported.includes(preferred) ? preferred : supported.includes("medium") ? "medium" : supported[0] ?? "off";
|
|
659
|
+
}
|
|
660
|
+
#validateMode(mode) {
|
|
661
|
+
if (mode !== void 0 && mode !== "interactive") throw new UnsupportedAgentOperationError("pi", `agent mode ${mode}`);
|
|
662
|
+
}
|
|
663
|
+
#configuration(runtime) {
|
|
664
|
+
return {
|
|
665
|
+
...runtime.session.model ? { modelId: modelId(runtime.session.model) } : {},
|
|
666
|
+
agentMode: "interactive",
|
|
667
|
+
reasoningEffort: runtime.session.thinkingLevel,
|
|
668
|
+
configOptions: this.#configOptions(runtime.session.sessionManager.getCwd())
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
#summary(info, userId, machineId) {
|
|
672
|
+
return {
|
|
673
|
+
userId,
|
|
674
|
+
machineId,
|
|
675
|
+
sessionId: info.id,
|
|
676
|
+
renameSupported: true,
|
|
677
|
+
summary: (info.name ?? info.firstMessage).slice(0, 4096),
|
|
678
|
+
workingDirectory: info.cwd,
|
|
679
|
+
isRemote: false,
|
|
680
|
+
createdAt: info.created.toISOString(),
|
|
681
|
+
modifiedAt: info.modified.toISOString()
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
async #refreshSessions() {
|
|
685
|
+
const root = join(this.#agentDirectory, "sessions");
|
|
686
|
+
let directories;
|
|
687
|
+
try {
|
|
688
|
+
directories = await readdir(root, { withFileTypes: true });
|
|
689
|
+
} catch (error) {
|
|
690
|
+
if (!isMissing(error)) throw error;
|
|
691
|
+
directories = [];
|
|
692
|
+
}
|
|
693
|
+
const lists = await Promise.all(directories.filter((entry) => entry.isDirectory()).map((entry) => SessionManager.listAll(join(root, entry.name))));
|
|
694
|
+
const next = /* @__PURE__ */ new Map();
|
|
695
|
+
for (const info of lists.flat()) {
|
|
696
|
+
if (next.has(info.id) && next.get(info.id).path !== info.path) throw new Error(`Duplicate PI native session ID: ${info.id}`);
|
|
697
|
+
next.set(info.id, info);
|
|
698
|
+
}
|
|
699
|
+
for (const id of this.#prepared.keys()) {
|
|
700
|
+
const info = this.#sessions.get(id);
|
|
701
|
+
if (!next.has(id) && info) next.set(id, info);
|
|
702
|
+
}
|
|
703
|
+
for (const [id, runtime] of this.#runtimes) {
|
|
704
|
+
if (!next.has(id) && this.#sessions.has(id)) next.set(id, this.#sessions.get(id));
|
|
705
|
+
const info = next.get(id);
|
|
706
|
+
if (info && runtime.busy) info.modified = /* @__PURE__ */ new Date();
|
|
707
|
+
}
|
|
708
|
+
this.#sessions.clear();
|
|
709
|
+
for (const [id, info] of next) this.#sessions.set(id, info);
|
|
710
|
+
}
|
|
711
|
+
async #openKnownSession(sessionId) {
|
|
712
|
+
const prepared = this.#prepared.get(sessionId);
|
|
713
|
+
if (prepared) return prepared.manager;
|
|
714
|
+
let info = this.#sessions.get(sessionId);
|
|
715
|
+
if (!info) {
|
|
716
|
+
await this.#refreshSessions();
|
|
717
|
+
info = this.#sessions.get(sessionId);
|
|
718
|
+
}
|
|
719
|
+
if (!info) throw new Error(`Unknown PI session: ${sessionId}`);
|
|
720
|
+
const handle = await open(info.path, "r");
|
|
721
|
+
try {
|
|
722
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
723
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
724
|
+
const firstLine = buffer.subarray(0, bytesRead).toString("utf8").split("\n", 1)[0];
|
|
725
|
+
const header = JSON.parse(firstLine);
|
|
726
|
+
if (typeof header !== "object" || header === null || !("type" in header) || header.type !== "session" || !("id" in header) || header.id !== sessionId || !("cwd" in header) || typeof header.cwd !== "string" || header.cwd !== info.cwd) {
|
|
727
|
+
throw new Error(`PI session header does not match the known session: ${sessionId}`);
|
|
728
|
+
}
|
|
729
|
+
} finally {
|
|
730
|
+
await handle.close();
|
|
731
|
+
}
|
|
732
|
+
const manager = SessionManager.open(info.path, dirname(info.path));
|
|
733
|
+
if (manager.getSessionId() !== sessionId) throw new Error(`PI session changed while opening: ${sessionId}`);
|
|
734
|
+
return manager;
|
|
735
|
+
}
|
|
736
|
+
async #runtime(sessionId) {
|
|
737
|
+
this.#assertStarted();
|
|
738
|
+
const existing = this.#runtimes.get(sessionId);
|
|
739
|
+
if (existing) return existing;
|
|
740
|
+
let opening = this.#opening.get(sessionId);
|
|
741
|
+
if (!opening) {
|
|
742
|
+
opening = (async () => {
|
|
743
|
+
const manager = await this.#openKnownSession(sessionId);
|
|
744
|
+
if (!(await stat(manager.getCwd())).isDirectory()) throw new Error(`PI workspace is not a directory: ${manager.getCwd()}`);
|
|
745
|
+
const prepared = this.#prepared.get(sessionId);
|
|
746
|
+
if (prepared) {
|
|
747
|
+
const runtime2 = await this.#createRuntime(manager, this.#model(prepared.modelId), prepared.thinking, this.#settings(manager.getCwd()));
|
|
748
|
+
this.#runtimes.set(sessionId, runtime2);
|
|
749
|
+
this.#prepared.delete(sessionId);
|
|
750
|
+
return runtime2;
|
|
751
|
+
}
|
|
752
|
+
const context = manager.buildSessionContext();
|
|
753
|
+
if (!context.model) throw new Error(`PI session has no saved model: ${sessionId}`);
|
|
754
|
+
const model = this.#model(`${context.model.provider}/${context.model.modelId}`);
|
|
755
|
+
const supported = getSupportedThinkingLevels(model);
|
|
756
|
+
if (!supported.some((level) => level === context.thinkingLevel)) throw new Error(`PI session has unsupported saved thinking level: ${context.thinkingLevel}`);
|
|
757
|
+
const runtime = await this.#createRuntime(manager, model, context.thinkingLevel, this.#settings(manager.getCwd()));
|
|
758
|
+
this.#runtimes.set(sessionId, runtime);
|
|
759
|
+
return runtime;
|
|
760
|
+
})();
|
|
761
|
+
this.#opening.set(sessionId, opening);
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
return await opening;
|
|
765
|
+
} finally {
|
|
766
|
+
if (this.#opening.get(sessionId) === opening) this.#opening.delete(sessionId);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async #createRuntime(manager, model, thinking, settings) {
|
|
770
|
+
const sessionId = manager.getSessionId();
|
|
771
|
+
this.#assertOpening(sessionId);
|
|
772
|
+
const cancellation = (pi) => {
|
|
773
|
+
const cancelled = () => this.#stopping || this.#cancelledOpenings.has(sessionId) || this.#runtimes.get(sessionId)?.aborting === true;
|
|
774
|
+
pi.on("session_before_compact", () => cancelled() ? { cancel: true } : void 0);
|
|
775
|
+
pi.on("session_before_tree", () => cancelled() ? { cancel: true } : void 0);
|
|
776
|
+
};
|
|
777
|
+
const loader = new DefaultResourceLoader({
|
|
778
|
+
cwd: manager.getCwd(),
|
|
779
|
+
agentDir: this.#agentDirectory,
|
|
780
|
+
settingsManager: settings,
|
|
781
|
+
extensionFactories: [cancellation]
|
|
782
|
+
});
|
|
783
|
+
await loader.reload({
|
|
784
|
+
resolveProjectTrust: ({ extensionsResult }) => this.#resolveProjectTrust(sessionId, manager.getCwd(), settings, extensionsResult)
|
|
785
|
+
});
|
|
786
|
+
this.#assertOpening(sessionId);
|
|
787
|
+
this.#checkSettings(settings);
|
|
788
|
+
const extensions = loader.getExtensions();
|
|
789
|
+
if (extensions.errors.length) {
|
|
790
|
+
throw new AggregateError(extensions.errors.map((error) => new Error(`${error.path}: ${error.error}`)), "Unable to load PI extensions.");
|
|
791
|
+
}
|
|
792
|
+
const result = await (this.#dependencies.createSession ?? createAgentSession)({
|
|
793
|
+
cwd: manager.getCwd(),
|
|
794
|
+
agentDir: this.#agentDirectory,
|
|
795
|
+
modelRuntime: this.#modelRuntime,
|
|
796
|
+
model,
|
|
797
|
+
thinkingLevel: thinking === "default" ? this.#thinking(model, "default", settings) : thinking,
|
|
798
|
+
sessionManager: manager,
|
|
799
|
+
settingsManager: settings,
|
|
800
|
+
resourceLoader: loader
|
|
801
|
+
});
|
|
802
|
+
const session = result.session;
|
|
803
|
+
try {
|
|
804
|
+
this.#assertOpening(sessionId);
|
|
805
|
+
if (result.modelFallbackMessage) throw new Error(result.modelFallbackMessage);
|
|
806
|
+
if (session.sessionId !== sessionId) throw new Error("PI SDK returned a different native session ID.");
|
|
807
|
+
const runtime = {
|
|
808
|
+
session,
|
|
809
|
+
timeline: new PiTimeline(sessionId, manager.getBranch()),
|
|
810
|
+
unsubscribe: () => {
|
|
811
|
+
},
|
|
812
|
+
busy: false,
|
|
813
|
+
aborting: false,
|
|
814
|
+
queue: [],
|
|
815
|
+
work: void 0,
|
|
816
|
+
dispatch: Promise.resolve(),
|
|
817
|
+
primarySource: void 0,
|
|
818
|
+
steeringSources: [],
|
|
819
|
+
followUpSources: []
|
|
820
|
+
};
|
|
821
|
+
const transformContext = session.agent.transformContext;
|
|
822
|
+
session.agent.transformContext = async (messages, signal) => {
|
|
823
|
+
const checkCancellation = () => {
|
|
824
|
+
if (runtime.aborting || this.#stopping || this.#cancelledOpenings.has(sessionId)) {
|
|
825
|
+
session.agent.abort();
|
|
826
|
+
throw new Error("PI request cancelled.");
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
checkCancellation();
|
|
830
|
+
const transformed = transformContext ? await transformContext(messages, signal) : messages;
|
|
831
|
+
checkCancellation();
|
|
832
|
+
return transformed;
|
|
833
|
+
};
|
|
834
|
+
runtime.unsubscribe = session.subscribe((event) => this.#event(runtime, event));
|
|
835
|
+
await session.bindExtensions({
|
|
836
|
+
uiContext: this.#ui(sessionId, session.extensionRunner.getUIContext().theme),
|
|
837
|
+
mode: "rpc",
|
|
838
|
+
onError: (error) => this.#error(runtime, new Error(`PI extension ${error.event}: ${error.error}`))
|
|
839
|
+
});
|
|
840
|
+
if (this.#stopping || this.#cancelledOpenings.has(sessionId)) {
|
|
841
|
+
runtime.unsubscribe();
|
|
842
|
+
throw new PiOpeningCancelledError();
|
|
843
|
+
}
|
|
844
|
+
return runtime;
|
|
845
|
+
} catch (error) {
|
|
846
|
+
session.dispose();
|
|
847
|
+
throw error;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
#event(runtime, event) {
|
|
851
|
+
const id = runtime.session.sessionId;
|
|
852
|
+
try {
|
|
853
|
+
const source = event.type === "message_start" && event.message.role === "user" ? this.#claimPromptSource(runtime, event.message.content) : void 0;
|
|
854
|
+
for (const item of runtime.timeline.event(event, source?.id)) {
|
|
855
|
+
if (item.kind === "human-message" && source) {
|
|
856
|
+
runtime.session.sessionManager.appendCustomEntry(PI_PROMPT_SOURCE_ENTRY, {
|
|
857
|
+
projectionId: item.id,
|
|
858
|
+
messageId: source.id
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
this.#callbacks?.onConversationItem(id, item);
|
|
862
|
+
}
|
|
863
|
+
if ((runtime.aborting || this.#stopping) && (event.type === "compaction_start" || event.type === "auto_retry_start")) {
|
|
864
|
+
queueMicrotask(() => this.#cancelBackgroundOperations(runtime));
|
|
865
|
+
}
|
|
866
|
+
if (event.type === "agent_start") {
|
|
867
|
+
this.#callbacks?.onSessionActivity?.(id, "working", (/* @__PURE__ */ new Date()).toISOString());
|
|
868
|
+
this.#dispatchQueued(runtime);
|
|
869
|
+
} else if (event.type === "agent_end") {
|
|
870
|
+
this.#callbacks?.onSessionInventoryChanged(id);
|
|
871
|
+
} else if (event.type === "session_info_changed") {
|
|
872
|
+
const info = this.#sessions.get(id);
|
|
873
|
+
if (info) {
|
|
874
|
+
if (event.name === void 0) delete info.name;
|
|
875
|
+
else info.name = event.name;
|
|
876
|
+
}
|
|
877
|
+
this.#callbacks?.onSessionInventoryChanged(id);
|
|
878
|
+
} else if (event.type === "message_end" && event.message.role === "assistant" && event.message.stopReason === "error") {
|
|
879
|
+
this.#callbacks?.onSyncError(id, new Error(event.message.errorMessage ?? "PI model request failed."));
|
|
880
|
+
} else if (event.type === "auto_retry_start") {
|
|
881
|
+
this.#notice(runtime, "warning", "Retrying", `${event.attempt}/${event.maxAttempts}: ${event.errorMessage}`);
|
|
882
|
+
} else if (event.type === "auto_retry_end" && !event.success) {
|
|
883
|
+
this.#error(runtime, new Error(event.finalError ?? "PI retries exhausted."));
|
|
884
|
+
} else if (event.type === "compaction_end" && event.errorMessage) {
|
|
885
|
+
this.#error(runtime, new Error(event.errorMessage));
|
|
886
|
+
}
|
|
887
|
+
} catch (error) {
|
|
888
|
+
this.#callbacks?.onSyncError(id, error);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
#startPrompts(sessionId, message) {
|
|
892
|
+
const startup = { queue: [message], cancelled: false, work: Promise.resolve() };
|
|
893
|
+
this.#startingPrompts.set(sessionId, startup);
|
|
894
|
+
let handedOff = false;
|
|
895
|
+
startup.work = Promise.resolve().then(async () => {
|
|
896
|
+
if (startup.cancelled || this.#stopping) return;
|
|
897
|
+
this.#callbacks?.onSessionActivity?.(sessionId, "working", (/* @__PURE__ */ new Date()).toISOString());
|
|
898
|
+
const runtime = await this.#runtime(sessionId);
|
|
899
|
+
if (startup.cancelled || this.#stopping) return;
|
|
900
|
+
runtime.queue.push(...startup.queue);
|
|
901
|
+
startup.queue = [];
|
|
902
|
+
this.#startingPrompts.delete(sessionId);
|
|
903
|
+
handedOff = true;
|
|
904
|
+
this.#runNext(runtime);
|
|
905
|
+
}).catch((error) => {
|
|
906
|
+
if (startup.cancelled || this.#stopping) return;
|
|
907
|
+
this.#openingNotice(sessionId, "error", "PI request failed", error instanceof Error ? error.message : String(error));
|
|
908
|
+
this.#callbacks?.onSyncError(sessionId, error);
|
|
909
|
+
}).finally(() => {
|
|
910
|
+
startup.queue = [];
|
|
911
|
+
if (this.#startingPrompts.get(sessionId) === startup) this.#startingPrompts.delete(sessionId);
|
|
912
|
+
if (!handedOff) this.#callbacks?.onSessionActivity?.(sessionId, "idle", (/* @__PURE__ */ new Date()).toISOString());
|
|
913
|
+
this.#callbacks?.onSessionInventoryChanged(sessionId);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
#runNext(runtime) {
|
|
917
|
+
if (runtime.busy || runtime.aborting || this.#stopping) return;
|
|
918
|
+
const prompt = runtime.queue.shift();
|
|
919
|
+
if (!prompt) return;
|
|
920
|
+
runtime.busy = true;
|
|
921
|
+
this.#callbacks?.onSessionActivity?.(runtime.session.sessionId, "working", (/* @__PURE__ */ new Date()).toISOString());
|
|
922
|
+
runtime.work = Promise.resolve().then(() => {
|
|
923
|
+
if (runtime.aborting || this.#stopping) return;
|
|
924
|
+
runtime.primarySource = prompt;
|
|
925
|
+
return runtime.session.prompt(prompt.text, {
|
|
926
|
+
expandPromptTemplates: false,
|
|
927
|
+
source: "rpc",
|
|
928
|
+
// PI awaits compaction/extensions before Agent.prompt; cancellation must survive preflight.
|
|
929
|
+
preflightResult: (accepted) => {
|
|
930
|
+
if (accepted && (runtime.aborting || this.#stopping)) throw new Error("PI prompt was aborted before execution.");
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
}).catch((error) => {
|
|
934
|
+
this.#error(runtime, error);
|
|
935
|
+
}).finally(async () => {
|
|
936
|
+
await runtime.dispatch;
|
|
937
|
+
if (runtime.primarySource?.id === prompt.id) runtime.primarySource = void 0;
|
|
938
|
+
runtime.busy = false;
|
|
939
|
+
this.#callbacks?.onSessionActivity?.(runtime.session.sessionId, "idle", (/* @__PURE__ */ new Date()).toISOString());
|
|
940
|
+
this.#callbacks?.onSessionInventoryChanged(runtime.session.sessionId);
|
|
941
|
+
this.#runNext(runtime);
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
#dispatchQueued(runtime) {
|
|
945
|
+
if (!runtime.session.isStreaming || runtime.aborting || this.#stopping) return;
|
|
946
|
+
runtime.dispatch = runtime.dispatch.then(async () => {
|
|
947
|
+
while (runtime.queue.length && runtime.session.isStreaming && !runtime.aborting && !this.#stopping) {
|
|
948
|
+
const prompt = runtime.queue.shift();
|
|
949
|
+
const sources = prompt.mode === "immediate" ? runtime.steeringSources : runtime.followUpSources;
|
|
950
|
+
sources.push(prompt);
|
|
951
|
+
try {
|
|
952
|
+
if (prompt.mode === "immediate") await runtime.session.steer(prompt.text);
|
|
953
|
+
else await runtime.session.followUp(prompt.text);
|
|
954
|
+
} catch (error) {
|
|
955
|
+
const index = sources.findIndex((source) => source.id === prompt.id);
|
|
956
|
+
if (index !== -1) sources.splice(index, 1);
|
|
957
|
+
this.#error(runtime, error);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
async #abort(runtime) {
|
|
963
|
+
runtime.aborting = true;
|
|
964
|
+
runtime.queue = [];
|
|
965
|
+
this.#dismissPending(runtime.session.sessionId);
|
|
966
|
+
runtime.session.clearQueue();
|
|
967
|
+
this.#cancelBackgroundOperations(runtime);
|
|
968
|
+
try {
|
|
969
|
+
await runtime.session.abort();
|
|
970
|
+
await runtime.dispatch;
|
|
971
|
+
await runtime.work;
|
|
972
|
+
runtime.session.clearQueue();
|
|
973
|
+
} finally {
|
|
974
|
+
runtime.primarySource = void 0;
|
|
975
|
+
runtime.steeringSources = [];
|
|
976
|
+
runtime.followUpSources = [];
|
|
977
|
+
runtime.aborting = false;
|
|
978
|
+
runtime.busy = false;
|
|
979
|
+
this.#callbacks?.onSessionActivity?.(runtime.session.sessionId, "idle", (/* @__PURE__ */ new Date()).toISOString());
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
#cancelBackgroundOperations(runtime) {
|
|
983
|
+
runtime.session.abortCompaction();
|
|
984
|
+
runtime.session.abortBranchSummary();
|
|
985
|
+
runtime.session.abortRetry();
|
|
986
|
+
}
|
|
987
|
+
#claimPromptSource(runtime, content) {
|
|
988
|
+
const text = typeof content === "string" ? content : content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
989
|
+
if (runtime.primarySource?.text === text) {
|
|
990
|
+
const source = runtime.primarySource;
|
|
991
|
+
runtime.primarySource = void 0;
|
|
992
|
+
return source;
|
|
993
|
+
}
|
|
994
|
+
for (const sources of [runtime.steeringSources, runtime.followUpSources]) {
|
|
995
|
+
const index = sources.findIndex((source) => source.text === text);
|
|
996
|
+
if (index !== -1) return sources.splice(index, 1)[0];
|
|
997
|
+
}
|
|
998
|
+
return void 0;
|
|
999
|
+
}
|
|
1000
|
+
#notice(runtime, severity, title, content) {
|
|
1001
|
+
const item = runtime.timeline.notice(`pi:${randomUUID()}`, (/* @__PURE__ */ new Date()).toISOString(), severity, title, content);
|
|
1002
|
+
this.#callbacks?.onConversationItem(runtime.session.sessionId, item);
|
|
1003
|
+
}
|
|
1004
|
+
#openingNotice(sessionId, severity, title, content) {
|
|
1005
|
+
let timeline = this.#openingNotices.get(sessionId);
|
|
1006
|
+
if (!timeline) {
|
|
1007
|
+
timeline = new PiTimeline(sessionId, []);
|
|
1008
|
+
this.#openingNotices.set(sessionId, timeline);
|
|
1009
|
+
}
|
|
1010
|
+
this.#callbacks?.onConversationItem(
|
|
1011
|
+
sessionId,
|
|
1012
|
+
timeline.notice(`pi:${randomUUID()}`, (/* @__PURE__ */ new Date()).toISOString(), severity, title, content)
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
#error(runtime, error) {
|
|
1016
|
+
this.#notice(runtime, "error", "PI request failed", error instanceof Error ? error.message : String(error));
|
|
1017
|
+
this.#callbacks?.onSyncError(runtime.session.sessionId, error);
|
|
1018
|
+
}
|
|
1019
|
+
async #resolveProjectTrust(sessionId, cwd, settings, extensions) {
|
|
1020
|
+
this.#checkSettings(settings);
|
|
1021
|
+
this.#assertOpening(sessionId);
|
|
1022
|
+
if (extensions.errors.length) {
|
|
1023
|
+
throw new AggregateError(extensions.errors.map((error) => new Error(`${error.path}: ${error.error}`)), "Unable to load PI project trust extensions.");
|
|
1024
|
+
}
|
|
1025
|
+
if (!hasTrustRequiringProjectResources(cwd)) return true;
|
|
1026
|
+
const store = new ProjectTrustStore(this.#agentDirectory);
|
|
1027
|
+
const context = {
|
|
1028
|
+
cwd,
|
|
1029
|
+
mode: "rpc",
|
|
1030
|
+
hasUI: this.#callbacks?.onInteractionRequest !== void 0,
|
|
1031
|
+
ui: this.#dialogUI(sessionId)
|
|
1032
|
+
};
|
|
1033
|
+
for (const extension of extensions.extensions) {
|
|
1034
|
+
for (const handler of extension.handlers.get("project_trust") ?? []) {
|
|
1035
|
+
let result;
|
|
1036
|
+
try {
|
|
1037
|
+
result = await handler({ type: "project_trust", cwd }, context);
|
|
1038
|
+
} catch (cause) {
|
|
1039
|
+
throw new Error(`PI extension ${extension.path} project_trust failed.`, { cause });
|
|
1040
|
+
}
|
|
1041
|
+
this.#assertOpening(sessionId);
|
|
1042
|
+
if (typeof result !== "object" || result === null || !("trusted" in result) || result.trusted !== "yes" && result.trusted !== "no" && result.trusted !== "undecided") {
|
|
1043
|
+
throw new Error(`PI extension ${extension.path} returned an invalid project trust decision.`);
|
|
1044
|
+
}
|
|
1045
|
+
if (result.trusted === "undecided") continue;
|
|
1046
|
+
const trusted = result.trusted === "yes";
|
|
1047
|
+
if ("remember" in result && result.remember === true) store.set(cwd, trusted);
|
|
1048
|
+
return trusted;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
const saved = store.get(cwd);
|
|
1052
|
+
if (saved !== null) return saved;
|
|
1053
|
+
const defaultTrust = settings.getDefaultProjectTrust();
|
|
1054
|
+
if (defaultTrust !== "ask") return defaultTrust === "always";
|
|
1055
|
+
const path = await realpath(cwd);
|
|
1056
|
+
const parent = dirname(path);
|
|
1057
|
+
const choices = [
|
|
1058
|
+
{ label: "Trust", trusted: true, updates: [{ path, decision: true }] },
|
|
1059
|
+
...parent !== path ? [{
|
|
1060
|
+
label: `Trust parent folder (${parent})`,
|
|
1061
|
+
trusted: true,
|
|
1062
|
+
updates: [{ path: parent, decision: true }, { path, decision: null }]
|
|
1063
|
+
}] : [],
|
|
1064
|
+
{ label: "Trust (this session only)", trusted: true, updates: [] },
|
|
1065
|
+
{ label: "Do not trust", trusted: false, updates: [{ path, decision: false }] },
|
|
1066
|
+
{ label: "Do not trust (this session only)", trusted: false, updates: [] }
|
|
1067
|
+
];
|
|
1068
|
+
const response = await context.ui.select(`Trust project folder?
|
|
1069
|
+
${cwd}`, choices.map((choice2) => choice2.label));
|
|
1070
|
+
const choice = choices.find((candidate) => candidate.label === response);
|
|
1071
|
+
this.#assertOpening(sessionId);
|
|
1072
|
+
if (!choice) throw new PiOpeningCancelledError();
|
|
1073
|
+
if (choice.updates.length) store.setMany(choice.updates);
|
|
1074
|
+
return choice.trusted;
|
|
1075
|
+
}
|
|
1076
|
+
#assertOpening(sessionId) {
|
|
1077
|
+
if (this.#stopping || this.#cancelledOpenings.has(sessionId)) throw new PiOpeningCancelledError();
|
|
1078
|
+
}
|
|
1079
|
+
#request(sessionId, request, options = {}) {
|
|
1080
|
+
InteractionRequestSchema.parse(request);
|
|
1081
|
+
if (this.#stopping || this.#cancelledOpenings.has(sessionId) || options.signal?.aborted || this.#runtimes.get(sessionId)?.aborting) return Promise.resolve(void 0);
|
|
1082
|
+
if (!this.#callbacks?.onInteractionRequest) return Promise.resolve(void 0);
|
|
1083
|
+
const id = randomUUID();
|
|
1084
|
+
return new Promise((resolveRequest) => {
|
|
1085
|
+
let done = false;
|
|
1086
|
+
const abort = () => finish(void 0);
|
|
1087
|
+
const timer = setTimeout(abort, Math.min(options.timeout ?? this.#timeout, this.#timeout));
|
|
1088
|
+
timer.unref();
|
|
1089
|
+
const finish = (response) => {
|
|
1090
|
+
if (done) return;
|
|
1091
|
+
done = true;
|
|
1092
|
+
clearTimeout(timer);
|
|
1093
|
+
options.signal?.removeEventListener("abort", abort);
|
|
1094
|
+
this.#pending.delete(id);
|
|
1095
|
+
resolveRequest(response);
|
|
1096
|
+
this.#callbacks?.onInteractionDismissed?.(sessionId, id);
|
|
1097
|
+
};
|
|
1098
|
+
const pending = { sessionId, request, finish };
|
|
1099
|
+
this.#pending.set(id, pending);
|
|
1100
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
1101
|
+
try {
|
|
1102
|
+
this.#publishInteraction(id, pending);
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
finish(void 0);
|
|
1105
|
+
this.#callbacks?.onSyncError(sessionId, error);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
#publishInteraction(id, pending) {
|
|
1110
|
+
if (this.#callbacks?.onInteractionRequest) this.#callbacks.onInteractionRequest(pending.sessionId, id, pending.request);
|
|
1111
|
+
else pending.finish(void 0);
|
|
1112
|
+
}
|
|
1113
|
+
#dismissPending(sessionId) {
|
|
1114
|
+
for (const pending of this.#pending.values()) if (sessionId === void 0 || pending.sessionId === sessionId) pending.finish(void 0);
|
|
1115
|
+
}
|
|
1116
|
+
#dialogUI(sessionId) {
|
|
1117
|
+
return {
|
|
1118
|
+
select: async (title, choices, options) => {
|
|
1119
|
+
const response = await this.#request(sessionId, { kind: "user_input", question: title, choices, allowFreeform: false }, options);
|
|
1120
|
+
return response?.kind === "user_input" ? response.answer : void 0;
|
|
1121
|
+
},
|
|
1122
|
+
confirm: async (title, message, options) => {
|
|
1123
|
+
const response = await this.#request(sessionId, {
|
|
1124
|
+
kind: "user_input",
|
|
1125
|
+
question: [title, message].filter(Boolean).join("\n"),
|
|
1126
|
+
choices: ["Confirm", "Cancel"],
|
|
1127
|
+
allowFreeform: false
|
|
1128
|
+
}, options);
|
|
1129
|
+
return response?.kind === "user_input" && response.answer === "Confirm";
|
|
1130
|
+
},
|
|
1131
|
+
input: async (title, _placeholder, options) => {
|
|
1132
|
+
const response = await this.#request(sessionId, { kind: "user_input", question: title, choices: [], allowFreeform: true }, options);
|
|
1133
|
+
return response?.kind === "user_input" ? response.answer : void 0;
|
|
1134
|
+
},
|
|
1135
|
+
notify: (message, severity = "info") => {
|
|
1136
|
+
const runtime = this.#runtimes.get(sessionId);
|
|
1137
|
+
if (runtime) this.#notice(runtime, severity, "PI", message);
|
|
1138
|
+
else this.#openingNotice(sessionId, severity, "PI", message);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
#ui(sessionId, theme) {
|
|
1143
|
+
const unsupported = () => {
|
|
1144
|
+
throw new UnsupportedAgentOperationError("pi", "terminal extension UI");
|
|
1145
|
+
};
|
|
1146
|
+
return {
|
|
1147
|
+
...this.#dialogUI(sessionId),
|
|
1148
|
+
onTerminalInput: unsupported,
|
|
1149
|
+
setStatus: unsupported,
|
|
1150
|
+
setWorkingMessage: unsupported,
|
|
1151
|
+
setWorkingVisible: unsupported,
|
|
1152
|
+
setWorkingIndicator: unsupported,
|
|
1153
|
+
setHiddenThinkingLabel: unsupported,
|
|
1154
|
+
setWidget: unsupported,
|
|
1155
|
+
setFooter: unsupported,
|
|
1156
|
+
setHeader: unsupported,
|
|
1157
|
+
setTitle: unsupported,
|
|
1158
|
+
custom: unsupported,
|
|
1159
|
+
pasteToEditor: unsupported,
|
|
1160
|
+
setEditorText: unsupported,
|
|
1161
|
+
getEditorText: unsupported,
|
|
1162
|
+
editor: unsupported,
|
|
1163
|
+
addAutocompleteProvider: unsupported,
|
|
1164
|
+
setEditorComponent: unsupported,
|
|
1165
|
+
getEditorComponent: unsupported,
|
|
1166
|
+
theme,
|
|
1167
|
+
getAllThemes: unsupported,
|
|
1168
|
+
getTheme: unsupported,
|
|
1169
|
+
setTheme: unsupported,
|
|
1170
|
+
getToolsExpanded: unsupported,
|
|
1171
|
+
setToolsExpanded: unsupported
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
export {
|
|
1176
|
+
PiCodingAgentAdapter
|
|
1177
|
+
};
|