sandbox-agent 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-7BNDCDDU.js +18 -0
- package/dist/chunk-7BNDCDDU.js.map +1 -0
- package/dist/chunk-TVCDKGSM.js +3029 -0
- package/dist/chunk-TVCDKGSM.js.map +1 -0
- package/dist/index.d.ts +1674 -144
- package/dist/index.js +17 -2353
- package/dist/index.js.map +1 -1
- package/dist/providers/cloudflare.d.ts +1 -1
- package/dist/providers/cloudflare.js +1 -0
- package/dist/providers/cloudflare.js.map +1 -1
- package/dist/providers/computesdk.d.ts +4 -4
- package/dist/providers/computesdk.js +9 -3
- package/dist/providers/computesdk.js.map +1 -1
- package/dist/providers/daytona.d.ts +3 -2
- package/dist/providers/daytona.js +4 -1
- package/dist/providers/daytona.js.map +1 -1
- package/dist/providers/docker.d.ts +1 -1
- package/dist/providers/docker.js +2 -1
- package/dist/providers/docker.js.map +1 -1
- package/dist/providers/e2b.d.ts +10 -3
- package/dist/providers/e2b.js +55 -10
- package/dist/providers/e2b.js.map +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/modal.d.ts +9 -6
- package/dist/providers/modal.js +20 -14
- package/dist/providers/modal.js.map +1 -1
- package/dist/providers/sprites.d.ts +22 -0
- package/dist/providers/sprites.js +209 -0
- package/dist/providers/sprites.js.map +1 -0
- package/dist/providers/vercel.d.ts +1 -1
- package/dist/providers/vercel.js +2 -1
- package/dist/providers/vercel.js.map +1 -1
- package/dist/{types-DLlJOfyX.d.ts → types-DdcvY5CI.d.ts} +22 -0
- package/package.json +13 -4
- package/dist/chunk-TWTMX66J.js +0 -15
- package/dist/chunk-TWTMX66J.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2358 +1,20 @@
|
|
|
1
|
-
// src/client.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
eventsBySession = /* @__PURE__ */ new Map();
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.maxSessions = normalizeCap(options.maxSessions, DEFAULT_MAX_SESSIONS);
|
|
19
|
-
this.maxEventsPerSession = normalizeCap(options.maxEventsPerSession, DEFAULT_MAX_EVENTS_PER_SESSION);
|
|
20
|
-
}
|
|
21
|
-
async getSession(id) {
|
|
22
|
-
const session = this.sessions.get(id);
|
|
23
|
-
return session ? cloneSessionRecord(session) : void 0;
|
|
24
|
-
}
|
|
25
|
-
async listSessions(request = {}) {
|
|
26
|
-
const sorted = [...this.sessions.values()].sort((a, b) => {
|
|
27
|
-
if (a.createdAt !== b.createdAt) {
|
|
28
|
-
return a.createdAt - b.createdAt;
|
|
29
|
-
}
|
|
30
|
-
return a.id.localeCompare(b.id);
|
|
31
|
-
});
|
|
32
|
-
const page = paginate(sorted, request);
|
|
33
|
-
return {
|
|
34
|
-
items: page.items.map(cloneSessionRecord),
|
|
35
|
-
nextCursor: page.nextCursor
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
async updateSession(session) {
|
|
39
|
-
this.sessions.set(session.id, { ...session });
|
|
40
|
-
if (!this.eventsBySession.has(session.id)) {
|
|
41
|
-
this.eventsBySession.set(session.id, []);
|
|
42
|
-
}
|
|
43
|
-
if (this.sessions.size <= this.maxSessions) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const overflow = this.sessions.size - this.maxSessions;
|
|
47
|
-
const removable = [...this.sessions.values()].sort((a, b) => {
|
|
48
|
-
if (a.createdAt !== b.createdAt) {
|
|
49
|
-
return a.createdAt - b.createdAt;
|
|
50
|
-
}
|
|
51
|
-
return a.id.localeCompare(b.id);
|
|
52
|
-
}).slice(0, overflow).map((sessionToRemove) => sessionToRemove.id);
|
|
53
|
-
for (const sessionId of removable) {
|
|
54
|
-
this.sessions.delete(sessionId);
|
|
55
|
-
this.eventsBySession.delete(sessionId);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async listEvents(request) {
|
|
59
|
-
const all = [...this.eventsBySession.get(request.sessionId) ?? []].sort((a, b) => {
|
|
60
|
-
if (a.eventIndex !== b.eventIndex) {
|
|
61
|
-
return a.eventIndex - b.eventIndex;
|
|
62
|
-
}
|
|
63
|
-
return a.id.localeCompare(b.id);
|
|
64
|
-
});
|
|
65
|
-
const page = paginate(all, request);
|
|
66
|
-
return {
|
|
67
|
-
items: page.items.map(cloneSessionEvent),
|
|
68
|
-
nextCursor: page.nextCursor
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
async insertEvent(sessionId, event) {
|
|
72
|
-
const events = this.eventsBySession.get(sessionId) ?? [];
|
|
73
|
-
events.push(cloneSessionEvent(event));
|
|
74
|
-
if (events.length > this.maxEventsPerSession) {
|
|
75
|
-
events.splice(0, events.length - this.maxEventsPerSession);
|
|
76
|
-
}
|
|
77
|
-
this.eventsBySession.set(sessionId, events);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
function cloneSessionRecord(session) {
|
|
81
|
-
return {
|
|
82
|
-
...session,
|
|
83
|
-
sessionInit: session.sessionInit ? JSON.parse(JSON.stringify(session.sessionInit)) : void 0,
|
|
84
|
-
configOptions: session.configOptions ? JSON.parse(JSON.stringify(session.configOptions)) : void 0,
|
|
85
|
-
modes: session.modes ? JSON.parse(JSON.stringify(session.modes)) : session.modes
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function cloneSessionEvent(event) {
|
|
89
|
-
return {
|
|
90
|
-
...event,
|
|
91
|
-
payload: JSON.parse(JSON.stringify(event.payload))
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function normalizeCap(value, fallback) {
|
|
95
|
-
if (!Number.isFinite(value) || (value ?? 0) < 1) {
|
|
96
|
-
return fallback;
|
|
97
|
-
}
|
|
98
|
-
return Math.floor(value);
|
|
99
|
-
}
|
|
100
|
-
function paginate(items, request) {
|
|
101
|
-
const offset = parseCursor(request.cursor);
|
|
102
|
-
const limit = normalizeCap(request.limit, DEFAULT_LIST_LIMIT);
|
|
103
|
-
const slice = items.slice(offset, offset + limit);
|
|
104
|
-
const nextOffset = offset + slice.length;
|
|
105
|
-
return {
|
|
106
|
-
items: slice,
|
|
107
|
-
nextCursor: nextOffset < items.length ? String(nextOffset) : void 0
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function parseCursor(cursor) {
|
|
111
|
-
if (!cursor) {
|
|
112
|
-
return 0;
|
|
113
|
-
}
|
|
114
|
-
const parsed = Number.parseInt(cursor, 10);
|
|
115
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
116
|
-
return 0;
|
|
117
|
-
}
|
|
118
|
-
return parsed;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/client.ts
|
|
122
|
-
var API_PREFIX = "/v1";
|
|
123
|
-
var FS_PATH = `${API_PREFIX}/fs`;
|
|
124
|
-
var DEFAULT_BASE_URL = "http://sandbox-agent";
|
|
125
|
-
var DEFAULT_REPLAY_MAX_EVENTS = 50;
|
|
126
|
-
var DEFAULT_REPLAY_MAX_CHARS = 12e3;
|
|
127
|
-
var EVENT_INDEX_SCAN_EVENTS_LIMIT = 500;
|
|
128
|
-
var MAX_EVENT_INDEX_INSERT_RETRIES = 3;
|
|
129
|
-
var SESSION_CANCEL_METHOD = "session/cancel";
|
|
130
|
-
var MANUAL_CANCEL_ERROR = "Manual session/cancel calls are not allowed. Use destroySession(sessionId) instead.";
|
|
131
|
-
var HEALTH_WAIT_MIN_DELAY_MS = 500;
|
|
132
|
-
var HEALTH_WAIT_MAX_DELAY_MS = 15e3;
|
|
133
|
-
var HEALTH_WAIT_LOG_AFTER_MS = 5e3;
|
|
134
|
-
var HEALTH_WAIT_LOG_EVERY_MS = 1e4;
|
|
135
|
-
var HEALTH_WAIT_ENSURE_SERVER_AFTER_FAILURES = 3;
|
|
136
|
-
var SandboxAgentError = class extends Error {
|
|
137
|
-
status;
|
|
138
|
-
problem;
|
|
139
|
-
response;
|
|
140
|
-
constructor(status, problem, response) {
|
|
141
|
-
super(problem?.title ?? `Request failed with status ${status}`);
|
|
142
|
-
this.name = "SandboxAgentError";
|
|
143
|
-
this.status = status;
|
|
144
|
-
this.problem = problem;
|
|
145
|
-
this.response = response;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
var UnsupportedSessionCategoryError = class extends Error {
|
|
149
|
-
sessionId;
|
|
150
|
-
category;
|
|
151
|
-
availableCategories;
|
|
152
|
-
constructor(sessionId, category, availableCategories) {
|
|
153
|
-
super(`Session '${sessionId}' does not support category '${category}'. Available categories: ${availableCategories.join(", ") || "(none)"}`);
|
|
154
|
-
this.name = "UnsupportedSessionCategoryError";
|
|
155
|
-
this.sessionId = sessionId;
|
|
156
|
-
this.category = category;
|
|
157
|
-
this.availableCategories = availableCategories;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
var UnsupportedSessionValueError = class extends Error {
|
|
161
|
-
sessionId;
|
|
162
|
-
category;
|
|
163
|
-
configId;
|
|
164
|
-
requestedValue;
|
|
165
|
-
allowedValues;
|
|
166
|
-
constructor(sessionId, category, configId, requestedValue, allowedValues) {
|
|
167
|
-
super(
|
|
168
|
-
`Session '${sessionId}' does not support value '${requestedValue}' for category '${category}' (configId='${configId}'). Allowed values: ${allowedValues.join(", ") || "(none)"}`
|
|
169
|
-
);
|
|
170
|
-
this.name = "UnsupportedSessionValueError";
|
|
171
|
-
this.sessionId = sessionId;
|
|
172
|
-
this.category = category;
|
|
173
|
-
this.configId = configId;
|
|
174
|
-
this.requestedValue = requestedValue;
|
|
175
|
-
this.allowedValues = allowedValues;
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
var UnsupportedSessionConfigOptionError = class extends Error {
|
|
179
|
-
sessionId;
|
|
180
|
-
configId;
|
|
181
|
-
availableConfigIds;
|
|
182
|
-
constructor(sessionId, configId, availableConfigIds) {
|
|
183
|
-
super(`Session '${sessionId}' does not expose config option '${configId}'. Available configIds: ${availableConfigIds.join(", ") || "(none)"}`);
|
|
184
|
-
this.name = "UnsupportedSessionConfigOptionError";
|
|
185
|
-
this.sessionId = sessionId;
|
|
186
|
-
this.configId = configId;
|
|
187
|
-
this.availableConfigIds = availableConfigIds;
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
var UnsupportedPermissionReplyError = class extends Error {
|
|
191
|
-
permissionId;
|
|
192
|
-
requestedReply;
|
|
193
|
-
availableReplies;
|
|
194
|
-
constructor(permissionId, requestedReply, availableReplies) {
|
|
195
|
-
super(`Permission '${permissionId}' does not support reply '${requestedReply}'. Available replies: ${availableReplies.join(", ") || "(none)"}`);
|
|
196
|
-
this.name = "UnsupportedPermissionReplyError";
|
|
197
|
-
this.permissionId = permissionId;
|
|
198
|
-
this.requestedReply = requestedReply;
|
|
199
|
-
this.availableReplies = availableReplies;
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
var Session = class {
|
|
203
|
-
record;
|
|
204
|
-
sandbox;
|
|
205
|
-
constructor(sandbox, record) {
|
|
206
|
-
this.sandbox = sandbox;
|
|
207
|
-
this.record = { ...record };
|
|
208
|
-
}
|
|
209
|
-
get id() {
|
|
210
|
-
return this.record.id;
|
|
211
|
-
}
|
|
212
|
-
get agent() {
|
|
213
|
-
return this.record.agent;
|
|
214
|
-
}
|
|
215
|
-
get agentSessionId() {
|
|
216
|
-
return this.record.agentSessionId;
|
|
217
|
-
}
|
|
218
|
-
get lastConnectionId() {
|
|
219
|
-
return this.record.lastConnectionId;
|
|
220
|
-
}
|
|
221
|
-
get createdAt() {
|
|
222
|
-
return this.record.createdAt;
|
|
223
|
-
}
|
|
224
|
-
get destroyedAt() {
|
|
225
|
-
return this.record.destroyedAt;
|
|
226
|
-
}
|
|
227
|
-
async refresh() {
|
|
228
|
-
const latest = await this.sandbox.getSession(this.id);
|
|
229
|
-
if (!latest) {
|
|
230
|
-
throw new Error(`session '${this.id}' no longer exists`);
|
|
231
|
-
}
|
|
232
|
-
this.apply(latest.toRecord());
|
|
233
|
-
return this;
|
|
234
|
-
}
|
|
235
|
-
async rawSend(method, params = {}, options = {}) {
|
|
236
|
-
const updated = await this.sandbox.rawSendSessionMethod(this.id, method, params, options);
|
|
237
|
-
this.apply(updated.session.toRecord());
|
|
238
|
-
return updated.response;
|
|
239
|
-
}
|
|
240
|
-
async prompt(prompt) {
|
|
241
|
-
const response = await this.rawSend("session/prompt", { prompt });
|
|
242
|
-
return response;
|
|
243
|
-
}
|
|
244
|
-
async setMode(modeId) {
|
|
245
|
-
const updated = await this.sandbox.setSessionMode(this.id, modeId);
|
|
246
|
-
this.apply(updated.session.toRecord());
|
|
247
|
-
return updated.response;
|
|
248
|
-
}
|
|
249
|
-
async setConfigOption(configId, value) {
|
|
250
|
-
const updated = await this.sandbox.setSessionConfigOption(this.id, configId, value);
|
|
251
|
-
this.apply(updated.session.toRecord());
|
|
252
|
-
return updated.response;
|
|
253
|
-
}
|
|
254
|
-
async setModel(model) {
|
|
255
|
-
const updated = await this.sandbox.setSessionModel(this.id, model);
|
|
256
|
-
this.apply(updated.session.toRecord());
|
|
257
|
-
return updated.response;
|
|
258
|
-
}
|
|
259
|
-
async setThoughtLevel(thoughtLevel) {
|
|
260
|
-
const updated = await this.sandbox.setSessionThoughtLevel(this.id, thoughtLevel);
|
|
261
|
-
this.apply(updated.session.toRecord());
|
|
262
|
-
return updated.response;
|
|
263
|
-
}
|
|
264
|
-
async getConfigOptions() {
|
|
265
|
-
return this.sandbox.getSessionConfigOptions(this.id);
|
|
266
|
-
}
|
|
267
|
-
async getModes() {
|
|
268
|
-
return this.sandbox.getSessionModes(this.id);
|
|
269
|
-
}
|
|
270
|
-
onEvent(listener) {
|
|
271
|
-
return this.sandbox.onSessionEvent(this.id, listener);
|
|
272
|
-
}
|
|
273
|
-
onPermissionRequest(listener) {
|
|
274
|
-
return this.sandbox.onPermissionRequest(this.id, listener);
|
|
275
|
-
}
|
|
276
|
-
async respondPermission(permissionId, reply) {
|
|
277
|
-
await this.sandbox.respondPermission(permissionId, reply);
|
|
278
|
-
}
|
|
279
|
-
async rawRespondPermission(permissionId, response) {
|
|
280
|
-
await this.sandbox.rawRespondPermission(permissionId, response);
|
|
281
|
-
}
|
|
282
|
-
toRecord() {
|
|
283
|
-
return { ...this.record };
|
|
284
|
-
}
|
|
285
|
-
apply(record) {
|
|
286
|
-
this.record = { ...record };
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
var LiveAcpConnection = class _LiveAcpConnection {
|
|
290
|
-
connectionId;
|
|
291
|
-
agent;
|
|
292
|
-
acp;
|
|
293
|
-
sessionByLocalId = /* @__PURE__ */ new Map();
|
|
294
|
-
localByAgentSessionId = /* @__PURE__ */ new Map();
|
|
295
|
-
pendingNewSessionLocals = [];
|
|
296
|
-
pendingRequestSessionById = /* @__PURE__ */ new Map();
|
|
297
|
-
pendingReplayByLocalSessionId = /* @__PURE__ */ new Map();
|
|
298
|
-
lastAdapterExit = null;
|
|
299
|
-
lastAdapterExitAt = 0;
|
|
300
|
-
onObservedEnvelope;
|
|
301
|
-
onPermissionRequest;
|
|
302
|
-
constructor(agent, connectionId, acp, onObservedEnvelope, onPermissionRequest) {
|
|
303
|
-
this.agent = agent;
|
|
304
|
-
this.connectionId = connectionId;
|
|
305
|
-
this.acp = acp;
|
|
306
|
-
this.onObservedEnvelope = onObservedEnvelope;
|
|
307
|
-
this.onPermissionRequest = onPermissionRequest;
|
|
308
|
-
}
|
|
309
|
-
static async create(options) {
|
|
310
|
-
const connectionId = randomId();
|
|
311
|
-
let live = null;
|
|
312
|
-
const acp = new AcpHttpClient({
|
|
313
|
-
baseUrl: options.baseUrl,
|
|
314
|
-
token: options.token,
|
|
315
|
-
fetch: options.fetcher,
|
|
316
|
-
headers: options.headers,
|
|
317
|
-
transport: {
|
|
318
|
-
path: `${API_PREFIX}/acp/${encodeURIComponent(options.serverId)}`,
|
|
319
|
-
bootstrapQuery: { agent: options.agent }
|
|
320
|
-
},
|
|
321
|
-
client: {
|
|
322
|
-
requestPermission: async (request) => {
|
|
323
|
-
if (!live) {
|
|
324
|
-
return cancelledPermissionResponse();
|
|
325
|
-
}
|
|
326
|
-
return live.handlePermissionRequest(request);
|
|
327
|
-
},
|
|
328
|
-
sessionUpdate: async (_notification) => {
|
|
329
|
-
},
|
|
330
|
-
extNotification: async (method, params) => {
|
|
331
|
-
if (!live) return;
|
|
332
|
-
live.handleAdapterNotification(method, params);
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
onEnvelope: (envelope, direction) => {
|
|
336
|
-
if (!live) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
live.handleEnvelope(envelope, direction);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
live = new _LiveAcpConnection(options.agent, connectionId, acp, options.onObservedEnvelope, options.onPermissionRequest);
|
|
343
|
-
const initResult = await acp.initialize({
|
|
344
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
345
|
-
clientInfo: {
|
|
346
|
-
name: "sandbox-agent-sdk",
|
|
347
|
-
version: "v1"
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
if (initResult.authMethods && initResult.authMethods.length > 0) {
|
|
351
|
-
await autoAuthenticate(acp, initResult.authMethods);
|
|
352
|
-
}
|
|
353
|
-
return live;
|
|
354
|
-
}
|
|
355
|
-
async close() {
|
|
356
|
-
await this.acp.disconnect();
|
|
357
|
-
}
|
|
358
|
-
hasBoundSession(localSessionId, agentSessionId) {
|
|
359
|
-
const bound = this.sessionByLocalId.get(localSessionId);
|
|
360
|
-
if (!bound) {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
if (agentSessionId && bound !== agentSessionId) {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
bindSession(localSessionId, agentSessionId) {
|
|
369
|
-
this.sessionByLocalId.set(localSessionId, agentSessionId);
|
|
370
|
-
this.localByAgentSessionId.set(agentSessionId, localSessionId);
|
|
371
|
-
}
|
|
372
|
-
queueReplay(localSessionId, replayText) {
|
|
373
|
-
if (!replayText) {
|
|
374
|
-
this.pendingReplayByLocalSessionId.delete(localSessionId);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
this.pendingReplayByLocalSessionId.set(localSessionId, replayText);
|
|
378
|
-
}
|
|
379
|
-
async createRemoteSession(localSessionId, sessionInit) {
|
|
380
|
-
const createStartedAt = Date.now();
|
|
381
|
-
this.pendingNewSessionLocals.push(localSessionId);
|
|
382
|
-
try {
|
|
383
|
-
const response = await this.acp.newSession(sessionInit);
|
|
384
|
-
this.bindSession(localSessionId, response.sessionId);
|
|
385
|
-
return response;
|
|
386
|
-
} catch (error) {
|
|
387
|
-
const index = this.pendingNewSessionLocals.indexOf(localSessionId);
|
|
388
|
-
if (index !== -1) {
|
|
389
|
-
this.pendingNewSessionLocals.splice(index, 1);
|
|
390
|
-
}
|
|
391
|
-
const adapterExit = this.lastAdapterExit;
|
|
392
|
-
if (adapterExit && this.lastAdapterExitAt >= createStartedAt) {
|
|
393
|
-
const suffix = adapterExit.code == null ? "" : ` (code ${adapterExit.code})`;
|
|
394
|
-
throw new Error(`Agent process exited while creating session${suffix}`);
|
|
395
|
-
}
|
|
396
|
-
throw error;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
async sendSessionMethod(localSessionId, method, params, options) {
|
|
400
|
-
const agentSessionId = this.sessionByLocalId.get(localSessionId);
|
|
401
|
-
if (!agentSessionId) {
|
|
402
|
-
throw new Error(`session '${localSessionId}' is not bound to live ACP connection '${this.connectionId}'`);
|
|
403
|
-
}
|
|
404
|
-
const mappedParams = mapSessionParams(params, agentSessionId);
|
|
405
|
-
if (method === "session/prompt") {
|
|
406
|
-
const replayText = this.pendingReplayByLocalSessionId.get(localSessionId);
|
|
407
|
-
if (replayText) {
|
|
408
|
-
this.pendingReplayByLocalSessionId.delete(localSessionId);
|
|
409
|
-
injectReplayPrompt(mappedParams, replayText);
|
|
410
|
-
}
|
|
411
|
-
if (options.notification) {
|
|
412
|
-
await this.acp.extNotification(method, mappedParams);
|
|
413
|
-
return void 0;
|
|
414
|
-
}
|
|
415
|
-
return this.acp.prompt(mappedParams);
|
|
416
|
-
}
|
|
417
|
-
if (method === "session/cancel") {
|
|
418
|
-
await this.acp.cancel(mappedParams);
|
|
419
|
-
return void 0;
|
|
420
|
-
}
|
|
421
|
-
if (method === "session/set_mode") {
|
|
422
|
-
return this.acp.setSessionMode(mappedParams);
|
|
423
|
-
}
|
|
424
|
-
if (method === "session/set_config_option") {
|
|
425
|
-
return this.acp.setSessionConfigOption(mappedParams);
|
|
426
|
-
}
|
|
427
|
-
if (options.notification) {
|
|
428
|
-
await this.acp.extNotification(method, mappedParams);
|
|
429
|
-
return void 0;
|
|
430
|
-
}
|
|
431
|
-
return this.acp.extMethod(method, mappedParams);
|
|
432
|
-
}
|
|
433
|
-
handleEnvelope(envelope, direction) {
|
|
434
|
-
const localSessionId = this.resolveSessionId(envelope, direction);
|
|
435
|
-
this.onObservedEnvelope(this, envelope, direction, localSessionId);
|
|
436
|
-
}
|
|
437
|
-
handleAdapterNotification(method, params) {
|
|
438
|
-
if (method !== "_adapter/agent_exited") {
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
this.lastAdapterExit = {
|
|
442
|
-
success: params.success === true,
|
|
443
|
-
code: typeof params.code === "number" ? params.code : null
|
|
444
|
-
};
|
|
445
|
-
this.lastAdapterExitAt = Date.now();
|
|
446
|
-
}
|
|
447
|
-
async handlePermissionRequest(request) {
|
|
448
|
-
const agentSessionId = request.sessionId;
|
|
449
|
-
const localSessionId = this.localByAgentSessionId.get(agentSessionId);
|
|
450
|
-
if (!localSessionId) {
|
|
451
|
-
return cancelledPermissionResponse();
|
|
452
|
-
}
|
|
453
|
-
return this.onPermissionRequest(this, localSessionId, agentSessionId, clonePermissionRequest(request));
|
|
454
|
-
}
|
|
455
|
-
resolveSessionId(envelope, direction) {
|
|
456
|
-
const id = envelopeId(envelope);
|
|
457
|
-
const method = envelopeMethod(envelope);
|
|
458
|
-
if (direction === "outbound") {
|
|
459
|
-
if (id && method === "session/new") {
|
|
460
|
-
const localSessionId = this.pendingNewSessionLocals.shift() ?? null;
|
|
461
|
-
if (localSessionId) {
|
|
462
|
-
this.pendingRequestSessionById.set(id, localSessionId);
|
|
463
|
-
}
|
|
464
|
-
return localSessionId;
|
|
465
|
-
}
|
|
466
|
-
const localFromParams = this.localFromEnvelopeParams(envelope);
|
|
467
|
-
if (id && localFromParams) {
|
|
468
|
-
this.pendingRequestSessionById.set(id, localFromParams);
|
|
469
|
-
}
|
|
470
|
-
return localFromParams;
|
|
471
|
-
}
|
|
472
|
-
if (id) {
|
|
473
|
-
const pending = this.pendingRequestSessionById.get(id) ?? null;
|
|
474
|
-
if (pending) {
|
|
475
|
-
this.pendingRequestSessionById.delete(id);
|
|
476
|
-
const sessionIdFromResult = envelopeSessionIdFromResult(envelope);
|
|
477
|
-
if (sessionIdFromResult) {
|
|
478
|
-
this.bindSession(pending, sessionIdFromResult);
|
|
479
|
-
}
|
|
480
|
-
return pending;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return this.localFromEnvelopeParams(envelope);
|
|
484
|
-
}
|
|
485
|
-
localFromEnvelopeParams(envelope) {
|
|
486
|
-
const agentSessionId = envelopeSessionIdFromParams(envelope);
|
|
487
|
-
if (!agentSessionId) {
|
|
488
|
-
return null;
|
|
489
|
-
}
|
|
490
|
-
return this.localByAgentSessionId.get(agentSessionId) ?? null;
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
var ProcessTerminalSession = class {
|
|
494
|
-
socket;
|
|
495
|
-
closed;
|
|
496
|
-
readyListeners = /* @__PURE__ */ new Set();
|
|
497
|
-
dataListeners = /* @__PURE__ */ new Set();
|
|
498
|
-
exitListeners = /* @__PURE__ */ new Set();
|
|
499
|
-
errorListeners = /* @__PURE__ */ new Set();
|
|
500
|
-
closeListeners = /* @__PURE__ */ new Set();
|
|
501
|
-
closeSignalSent = false;
|
|
502
|
-
closedResolve;
|
|
503
|
-
constructor(socket) {
|
|
504
|
-
this.socket = socket;
|
|
505
|
-
this.socket.binaryType = "arraybuffer";
|
|
506
|
-
this.closed = new Promise((resolve) => {
|
|
507
|
-
this.closedResolve = resolve;
|
|
508
|
-
});
|
|
509
|
-
this.socket.addEventListener("message", (event) => {
|
|
510
|
-
void this.handleMessage(event.data);
|
|
511
|
-
});
|
|
512
|
-
this.socket.addEventListener("error", () => {
|
|
513
|
-
this.emitError(new Error("Terminal websocket connection failed."));
|
|
514
|
-
});
|
|
515
|
-
this.socket.addEventListener("close", () => {
|
|
516
|
-
this.closedResolve();
|
|
517
|
-
for (const listener of this.closeListeners) {
|
|
518
|
-
listener();
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
onReady(listener) {
|
|
523
|
-
this.readyListeners.add(listener);
|
|
524
|
-
return () => {
|
|
525
|
-
this.readyListeners.delete(listener);
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
onData(listener) {
|
|
529
|
-
this.dataListeners.add(listener);
|
|
530
|
-
return () => {
|
|
531
|
-
this.dataListeners.delete(listener);
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
onExit(listener) {
|
|
535
|
-
this.exitListeners.add(listener);
|
|
536
|
-
return () => {
|
|
537
|
-
this.exitListeners.delete(listener);
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
onError(listener) {
|
|
541
|
-
this.errorListeners.add(listener);
|
|
542
|
-
return () => {
|
|
543
|
-
this.errorListeners.delete(listener);
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
onClose(listener) {
|
|
547
|
-
this.closeListeners.add(listener);
|
|
548
|
-
return () => {
|
|
549
|
-
this.closeListeners.delete(listener);
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
sendInput(data) {
|
|
553
|
-
const payload = encodeTerminalInput(data);
|
|
554
|
-
this.sendFrame({
|
|
555
|
-
type: "input",
|
|
556
|
-
data: payload.data,
|
|
557
|
-
encoding: payload.encoding
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
resize(payload) {
|
|
561
|
-
this.sendFrame({
|
|
562
|
-
type: "resize",
|
|
563
|
-
cols: payload.cols,
|
|
564
|
-
rows: payload.rows
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
close() {
|
|
568
|
-
if (this.socket.readyState === WS_READY_STATE_CONNECTING) {
|
|
569
|
-
this.socket.addEventListener(
|
|
570
|
-
"open",
|
|
571
|
-
() => {
|
|
572
|
-
this.close();
|
|
573
|
-
},
|
|
574
|
-
{ once: true }
|
|
575
|
-
);
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
if (this.socket.readyState === WS_READY_STATE_OPEN) {
|
|
579
|
-
if (!this.closeSignalSent) {
|
|
580
|
-
this.closeSignalSent = true;
|
|
581
|
-
this.sendFrame({ type: "close" });
|
|
582
|
-
}
|
|
583
|
-
this.socket.close();
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
if (this.socket.readyState !== WS_READY_STATE_CLOSED) {
|
|
587
|
-
this.socket.close();
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
async handleMessage(data) {
|
|
591
|
-
try {
|
|
592
|
-
if (typeof data === "string") {
|
|
593
|
-
const frame = parseProcessTerminalServerFrame(data);
|
|
594
|
-
if (!frame) {
|
|
595
|
-
this.emitError(new Error("Received invalid terminal control frame."));
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
if (frame.type === "ready") {
|
|
599
|
-
for (const listener of this.readyListeners) {
|
|
600
|
-
listener(frame);
|
|
601
|
-
}
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (frame.type === "exit") {
|
|
605
|
-
for (const listener of this.exitListeners) {
|
|
606
|
-
listener(frame);
|
|
607
|
-
}
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
this.emitError(frame);
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
const bytes = await decodeTerminalBytes(data);
|
|
614
|
-
for (const listener of this.dataListeners) {
|
|
615
|
-
listener(bytes);
|
|
616
|
-
}
|
|
617
|
-
} catch (error) {
|
|
618
|
-
this.emitError(error instanceof Error ? error : new Error(String(error)));
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
sendFrame(frame) {
|
|
622
|
-
if (this.socket.readyState !== WS_READY_STATE_OPEN) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
this.socket.send(JSON.stringify(frame));
|
|
626
|
-
}
|
|
627
|
-
emitError(error) {
|
|
628
|
-
for (const listener of this.errorListeners) {
|
|
629
|
-
listener(error);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
var WS_READY_STATE_CONNECTING = 0;
|
|
634
|
-
var WS_READY_STATE_OPEN = 1;
|
|
635
|
-
var WS_READY_STATE_CLOSED = 3;
|
|
636
|
-
var SandboxAgent = class _SandboxAgent {
|
|
637
|
-
baseUrl;
|
|
638
|
-
token;
|
|
639
|
-
fetcher;
|
|
640
|
-
defaultHeaders;
|
|
641
|
-
healthWait;
|
|
642
|
-
healthWaitAbortController = new AbortController();
|
|
643
|
-
sandboxProvider;
|
|
644
|
-
sandboxProviderId;
|
|
645
|
-
sandboxProviderRawId;
|
|
646
|
-
persist;
|
|
647
|
-
replayMaxEvents;
|
|
648
|
-
replayMaxChars;
|
|
649
|
-
healthPromise;
|
|
650
|
-
healthError;
|
|
651
|
-
disposed = false;
|
|
652
|
-
liveConnections = /* @__PURE__ */ new Map();
|
|
653
|
-
pendingLiveConnections = /* @__PURE__ */ new Map();
|
|
654
|
-
sessionHandles = /* @__PURE__ */ new Map();
|
|
655
|
-
eventListeners = /* @__PURE__ */ new Map();
|
|
656
|
-
permissionListeners = /* @__PURE__ */ new Map();
|
|
657
|
-
pendingPermissionRequests = /* @__PURE__ */ new Map();
|
|
658
|
-
nextSessionEventIndexBySession = /* @__PURE__ */ new Map();
|
|
659
|
-
seedSessionEventIndexBySession = /* @__PURE__ */ new Map();
|
|
660
|
-
pendingObservedEnvelopePersistenceBySession = /* @__PURE__ */ new Map();
|
|
661
|
-
constructor(options) {
|
|
662
|
-
const baseUrl = options.baseUrl?.trim();
|
|
663
|
-
if (!baseUrl && !options.fetch) {
|
|
664
|
-
throw new Error("baseUrl is required unless fetch is provided.");
|
|
665
|
-
}
|
|
666
|
-
this.baseUrl = (baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
667
|
-
this.token = options.token;
|
|
668
|
-
const resolvedFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
669
|
-
if (!resolvedFetch) {
|
|
670
|
-
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
|
671
|
-
}
|
|
672
|
-
this.fetcher = resolvedFetch;
|
|
673
|
-
this.defaultHeaders = options.headers;
|
|
674
|
-
this.healthWait = normalizeHealthWaitOptions(options.skipHealthCheck, options.waitForHealth, options.signal);
|
|
675
|
-
this.persist = options.persist ?? new InMemorySessionPersistDriver();
|
|
676
|
-
this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
|
|
677
|
-
this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
|
|
678
|
-
this.startHealthWait();
|
|
679
|
-
}
|
|
680
|
-
static async connect(options) {
|
|
681
|
-
return new _SandboxAgent(options);
|
|
682
|
-
}
|
|
683
|
-
static async start(options) {
|
|
684
|
-
const provider = options.sandbox;
|
|
685
|
-
if (!provider.getUrl && !provider.getFetch) {
|
|
686
|
-
throw new Error(`Sandbox provider '${provider.name}' must implement getUrl() or getFetch().`);
|
|
687
|
-
}
|
|
688
|
-
const existingSandbox = options.sandboxId ? parseSandboxProviderId(options.sandboxId) : null;
|
|
689
|
-
if (existingSandbox && existingSandbox.provider !== provider.name) {
|
|
690
|
-
throw new Error(
|
|
691
|
-
`SandboxAgent.start received sandboxId '${options.sandboxId}' for provider '${existingSandbox.provider}', but the configured provider is '${provider.name}'.`
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
const rawSandboxId = existingSandbox?.rawId ?? await provider.create();
|
|
695
|
-
const prefixedSandboxId = `${provider.name}/${rawSandboxId}`;
|
|
696
|
-
const createdSandbox = !existingSandbox;
|
|
697
|
-
if (existingSandbox) {
|
|
698
|
-
await provider.ensureServer?.(rawSandboxId);
|
|
699
|
-
}
|
|
700
|
-
try {
|
|
701
|
-
const fetcher = await resolveProviderFetch(provider, rawSandboxId);
|
|
702
|
-
const baseUrl = provider.getUrl ? await provider.getUrl(rawSandboxId) : void 0;
|
|
703
|
-
const providerFetch = options.fetch ?? fetcher;
|
|
704
|
-
const commonConnectOptions = {
|
|
705
|
-
headers: options.headers,
|
|
706
|
-
persist: options.persist,
|
|
707
|
-
replayMaxEvents: options.replayMaxEvents,
|
|
708
|
-
replayMaxChars: options.replayMaxChars,
|
|
709
|
-
signal: options.signal,
|
|
710
|
-
skipHealthCheck: options.skipHealthCheck,
|
|
711
|
-
token: options.token ?? await resolveProviderToken(provider, rawSandboxId)
|
|
712
|
-
};
|
|
713
|
-
const client = providerFetch ? new _SandboxAgent({
|
|
714
|
-
...commonConnectOptions,
|
|
715
|
-
baseUrl,
|
|
716
|
-
fetch: providerFetch
|
|
717
|
-
}) : new _SandboxAgent({
|
|
718
|
-
...commonConnectOptions,
|
|
719
|
-
baseUrl: requireSandboxBaseUrl(baseUrl, provider.name)
|
|
720
|
-
});
|
|
721
|
-
client.sandboxProvider = provider;
|
|
722
|
-
client.sandboxProviderId = prefixedSandboxId;
|
|
723
|
-
client.sandboxProviderRawId = rawSandboxId;
|
|
724
|
-
return client;
|
|
725
|
-
} catch (error) {
|
|
726
|
-
if (createdSandbox) {
|
|
727
|
-
try {
|
|
728
|
-
await provider.destroy(rawSandboxId);
|
|
729
|
-
} catch {
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
throw error;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
get sandboxId() {
|
|
736
|
-
return this.sandboxProviderId;
|
|
737
|
-
}
|
|
738
|
-
get sandbox() {
|
|
739
|
-
return this.sandboxProvider;
|
|
740
|
-
}
|
|
741
|
-
get inspectorUrl() {
|
|
742
|
-
return `${this.baseUrl.replace(/\/+$/, "")}/ui/`;
|
|
743
|
-
}
|
|
744
|
-
async dispose() {
|
|
745
|
-
this.disposed = true;
|
|
746
|
-
this.healthWaitAbortController.abort(createAbortError("SandboxAgent was disposed."));
|
|
747
|
-
for (const [permissionId, pending2] of this.pendingPermissionRequests) {
|
|
748
|
-
this.pendingPermissionRequests.delete(permissionId);
|
|
749
|
-
pending2.resolve(cancelledPermissionResponse());
|
|
750
|
-
}
|
|
751
|
-
const connections = [...this.liveConnections.values()];
|
|
752
|
-
this.liveConnections.clear();
|
|
753
|
-
const pending = [...this.pendingLiveConnections.values()];
|
|
754
|
-
this.pendingLiveConnections.clear();
|
|
755
|
-
this.pendingObservedEnvelopePersistenceBySession.clear();
|
|
756
|
-
const pendingSettled = await Promise.allSettled(pending);
|
|
757
|
-
for (const item of pendingSettled) {
|
|
758
|
-
if (item.status === "fulfilled") {
|
|
759
|
-
connections.push(item.value);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
await Promise.all(
|
|
763
|
-
connections.map(async (connection) => {
|
|
764
|
-
await connection.close();
|
|
765
|
-
})
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
async destroySandbox() {
|
|
769
|
-
const provider = this.sandboxProvider;
|
|
770
|
-
const rawSandboxId = this.sandboxProviderRawId;
|
|
771
|
-
try {
|
|
772
|
-
if (provider && rawSandboxId) {
|
|
773
|
-
await provider.destroy(rawSandboxId);
|
|
774
|
-
} else if (!provider || !rawSandboxId) {
|
|
775
|
-
throw new Error("SandboxAgent is not attached to a provisioned sandbox.");
|
|
776
|
-
}
|
|
777
|
-
} finally {
|
|
778
|
-
await this.dispose();
|
|
779
|
-
this.sandboxProvider = void 0;
|
|
780
|
-
this.sandboxProviderId = void 0;
|
|
781
|
-
this.sandboxProviderRawId = void 0;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
async listSessions(request = {}) {
|
|
785
|
-
const page = await this.persist.listSessions(request);
|
|
786
|
-
return {
|
|
787
|
-
items: page.items.map((record) => this.upsertSessionHandle(record)),
|
|
788
|
-
nextCursor: page.nextCursor
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
async getSession(id) {
|
|
792
|
-
const record = await this.persist.getSession(id);
|
|
793
|
-
if (!record) {
|
|
794
|
-
return null;
|
|
795
|
-
}
|
|
796
|
-
return this.upsertSessionHandle(record);
|
|
797
|
-
}
|
|
798
|
-
async getEvents(request) {
|
|
799
|
-
return this.persist.listEvents(request);
|
|
800
|
-
}
|
|
801
|
-
async createSession(request) {
|
|
802
|
-
if (!request.agent.trim()) {
|
|
803
|
-
throw new Error("createSession requires a non-empty agent");
|
|
804
|
-
}
|
|
805
|
-
const localSessionId = request.id?.trim() || randomId();
|
|
806
|
-
const live = await this.getLiveConnection(request.agent.trim());
|
|
807
|
-
const sessionInit = normalizeSessionInit(request.sessionInit, request.cwd);
|
|
808
|
-
const response = await live.createRemoteSession(localSessionId, sessionInit);
|
|
809
|
-
const record = {
|
|
810
|
-
id: localSessionId,
|
|
811
|
-
agent: request.agent.trim(),
|
|
812
|
-
agentSessionId: response.sessionId,
|
|
813
|
-
lastConnectionId: live.connectionId,
|
|
814
|
-
createdAt: nowMs(),
|
|
815
|
-
sandboxId: this.sandboxProviderId,
|
|
816
|
-
sessionInit,
|
|
817
|
-
configOptions: cloneConfigOptions(response.configOptions),
|
|
818
|
-
modes: cloneModes(response.modes)
|
|
819
|
-
};
|
|
820
|
-
await this.persist.updateSession(record);
|
|
821
|
-
live.bindSession(record.id, record.agentSessionId);
|
|
822
|
-
let session = this.upsertSessionHandle(record);
|
|
823
|
-
try {
|
|
824
|
-
if (request.mode) {
|
|
825
|
-
session = (await this.setSessionMode(session.id, request.mode)).session;
|
|
826
|
-
}
|
|
827
|
-
if (request.model) {
|
|
828
|
-
session = (await this.setSessionModel(session.id, request.model)).session;
|
|
829
|
-
}
|
|
830
|
-
if (request.thoughtLevel) {
|
|
831
|
-
session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
|
|
832
|
-
}
|
|
833
|
-
} catch (err) {
|
|
834
|
-
try {
|
|
835
|
-
await this.destroySession(session.id);
|
|
836
|
-
} catch {
|
|
837
|
-
}
|
|
838
|
-
throw err;
|
|
839
|
-
}
|
|
840
|
-
return session;
|
|
841
|
-
}
|
|
842
|
-
async resumeSession(id) {
|
|
843
|
-
const existing = await this.persist.getSession(id);
|
|
844
|
-
if (!existing) {
|
|
845
|
-
throw new Error(`session '${id}' not found`);
|
|
846
|
-
}
|
|
847
|
-
const live = await this.getLiveConnection(existing.agent);
|
|
848
|
-
if (existing.lastConnectionId === live.connectionId && live.hasBoundSession(id, existing.agentSessionId)) {
|
|
849
|
-
return this.upsertSessionHandle(existing);
|
|
850
|
-
}
|
|
851
|
-
const replaySource = await this.collectReplayEvents(existing.id, this.replayMaxEvents);
|
|
852
|
-
const replayText = buildReplayText(replaySource, this.replayMaxChars);
|
|
853
|
-
const recreated = await live.createRemoteSession(existing.id, normalizeSessionInit(existing.sessionInit));
|
|
854
|
-
const updated = {
|
|
855
|
-
...existing,
|
|
856
|
-
agentSessionId: recreated.sessionId,
|
|
857
|
-
lastConnectionId: live.connectionId,
|
|
858
|
-
destroyedAt: void 0,
|
|
859
|
-
configOptions: cloneConfigOptions(recreated.configOptions),
|
|
860
|
-
modes: cloneModes(recreated.modes)
|
|
861
|
-
};
|
|
862
|
-
await this.persist.updateSession(updated);
|
|
863
|
-
live.bindSession(updated.id, updated.agentSessionId);
|
|
864
|
-
live.queueReplay(updated.id, replayText);
|
|
865
|
-
return this.upsertSessionHandle(updated);
|
|
866
|
-
}
|
|
867
|
-
async resumeOrCreateSession(request) {
|
|
868
|
-
const existing = await this.persist.getSession(request.id);
|
|
869
|
-
if (existing) {
|
|
870
|
-
let session = await this.resumeSession(existing.id);
|
|
871
|
-
if (request.mode) {
|
|
872
|
-
session = (await this.setSessionMode(session.id, request.mode)).session;
|
|
873
|
-
}
|
|
874
|
-
if (request.model) {
|
|
875
|
-
session = (await this.setSessionModel(session.id, request.model)).session;
|
|
876
|
-
}
|
|
877
|
-
if (request.thoughtLevel) {
|
|
878
|
-
session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
|
|
879
|
-
}
|
|
880
|
-
return session;
|
|
881
|
-
}
|
|
882
|
-
return this.createSession(request);
|
|
883
|
-
}
|
|
884
|
-
async destroySession(id) {
|
|
885
|
-
this.cancelPendingPermissionsForSession(id);
|
|
886
|
-
try {
|
|
887
|
-
await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true);
|
|
888
|
-
} catch {
|
|
889
|
-
}
|
|
890
|
-
const existing = await this.requireSessionRecord(id);
|
|
891
|
-
const updated = {
|
|
892
|
-
...existing,
|
|
893
|
-
destroyedAt: nowMs()
|
|
894
|
-
};
|
|
895
|
-
await this.persist.updateSession(updated);
|
|
896
|
-
return this.upsertSessionHandle(updated);
|
|
897
|
-
}
|
|
898
|
-
async setSessionMode(sessionId, modeId) {
|
|
899
|
-
const mode = modeId.trim();
|
|
900
|
-
if (!mode) {
|
|
901
|
-
throw new Error("setSessionMode requires a non-empty modeId");
|
|
902
|
-
}
|
|
903
|
-
const record = await this.requireSessionRecord(sessionId);
|
|
904
|
-
const knownModeIds = extractKnownModeIds(record.modes);
|
|
905
|
-
if (knownModeIds.length > 0 && !knownModeIds.includes(mode)) {
|
|
906
|
-
throw new UnsupportedSessionValueError(sessionId, "mode", "mode", mode, knownModeIds);
|
|
907
|
-
}
|
|
908
|
-
try {
|
|
909
|
-
return await this.sendSessionMethodInternal(sessionId, "session/set_mode", { modeId: mode }, {}, false);
|
|
910
|
-
} catch (error) {
|
|
911
|
-
if (!(error instanceof AcpRpcError) || error.code !== -32601) {
|
|
912
|
-
throw error;
|
|
913
|
-
}
|
|
914
|
-
return this.setSessionCategoryValue(sessionId, "mode", mode);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
async setSessionConfigOption(sessionId, configId, value) {
|
|
918
|
-
const resolvedConfigId = configId.trim();
|
|
919
|
-
if (!resolvedConfigId) {
|
|
920
|
-
throw new Error("setSessionConfigOption requires a non-empty configId");
|
|
921
|
-
}
|
|
922
|
-
const resolvedValue = value.trim();
|
|
923
|
-
if (!resolvedValue) {
|
|
924
|
-
throw new Error("setSessionConfigOption requires a non-empty value");
|
|
925
|
-
}
|
|
926
|
-
const options = await this.getSessionConfigOptions(sessionId);
|
|
927
|
-
const option = findConfigOptionById(options, resolvedConfigId);
|
|
928
|
-
if (!option) {
|
|
929
|
-
throw new UnsupportedSessionConfigOptionError(
|
|
930
|
-
sessionId,
|
|
931
|
-
resolvedConfigId,
|
|
932
|
-
options.map((item) => item.id)
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
const allowedValues = extractConfigValues(option);
|
|
936
|
-
if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
|
|
937
|
-
throw new UnsupportedSessionValueError(sessionId, option.category ?? "uncategorized", option.id, resolvedValue, allowedValues);
|
|
938
|
-
}
|
|
939
|
-
return await this.sendSessionMethodInternal(
|
|
940
|
-
sessionId,
|
|
941
|
-
"session/set_config_option",
|
|
942
|
-
{
|
|
943
|
-
configId: resolvedConfigId,
|
|
944
|
-
value: resolvedValue
|
|
945
|
-
},
|
|
946
|
-
{},
|
|
947
|
-
false
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
async setSessionModel(sessionId, model) {
|
|
951
|
-
return this.setSessionCategoryValue(sessionId, "model", model);
|
|
952
|
-
}
|
|
953
|
-
async setSessionThoughtLevel(sessionId, thoughtLevel) {
|
|
954
|
-
return this.setSessionCategoryValue(sessionId, "thought_level", thoughtLevel);
|
|
955
|
-
}
|
|
956
|
-
async getSessionConfigOptions(sessionId) {
|
|
957
|
-
const record = await this.requireSessionRecord(sessionId);
|
|
958
|
-
const hydrated = await this.hydrateSessionConfigOptions(record.id, record);
|
|
959
|
-
return cloneConfigOptions(hydrated.configOptions) ?? [];
|
|
960
|
-
}
|
|
961
|
-
async getSessionModes(sessionId) {
|
|
962
|
-
const record = await this.requireSessionRecord(sessionId);
|
|
963
|
-
if (record.modes && record.modes.availableModes.length > 0) {
|
|
964
|
-
return cloneModes(record.modes);
|
|
965
|
-
}
|
|
966
|
-
const hydrated = await this.hydrateSessionConfigOptions(record.id, record);
|
|
967
|
-
if (hydrated.modes && hydrated.modes.availableModes.length > 0) {
|
|
968
|
-
return cloneModes(hydrated.modes);
|
|
969
|
-
}
|
|
970
|
-
const derived = deriveModesFromConfigOptions(hydrated.configOptions);
|
|
971
|
-
if (!derived) {
|
|
972
|
-
return cloneModes(hydrated.modes);
|
|
973
|
-
}
|
|
974
|
-
const updated = {
|
|
975
|
-
...hydrated,
|
|
976
|
-
modes: derived
|
|
977
|
-
};
|
|
978
|
-
await this.persist.updateSession(updated);
|
|
979
|
-
return cloneModes(derived);
|
|
980
|
-
}
|
|
981
|
-
async setSessionCategoryValue(sessionId, category, value) {
|
|
982
|
-
const resolvedValue = value.trim();
|
|
983
|
-
if (!resolvedValue) {
|
|
984
|
-
throw new Error(`setSession${toTitleCase(category)} requires a non-empty value`);
|
|
985
|
-
}
|
|
986
|
-
const options = await this.getSessionConfigOptions(sessionId);
|
|
987
|
-
const option = findConfigOptionByCategory(options, category);
|
|
988
|
-
if (!option) {
|
|
989
|
-
const categories = uniqueCategories(options);
|
|
990
|
-
throw new UnsupportedSessionCategoryError(sessionId, category, categories);
|
|
991
|
-
}
|
|
992
|
-
const allowedValues = extractConfigValues(option);
|
|
993
|
-
if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
|
|
994
|
-
throw new UnsupportedSessionValueError(sessionId, category, option.id, resolvedValue, allowedValues);
|
|
995
|
-
}
|
|
996
|
-
return this.setSessionConfigOption(sessionId, option.id, resolvedValue);
|
|
997
|
-
}
|
|
998
|
-
async hydrateSessionConfigOptions(sessionId, snapshot) {
|
|
999
|
-
if (snapshot.configOptions !== void 0) {
|
|
1000
|
-
return snapshot;
|
|
1001
|
-
}
|
|
1002
|
-
const info = await this.getAgent(snapshot.agent, { config: true });
|
|
1003
|
-
let configOptions = normalizeSessionConfigOptions(info.configOptions) ?? [];
|
|
1004
|
-
const record = await this.persist.getSession(sessionId);
|
|
1005
|
-
if (!record) {
|
|
1006
|
-
return { ...snapshot, configOptions };
|
|
1007
|
-
}
|
|
1008
|
-
const currentModeId = record.modes?.currentModeId;
|
|
1009
|
-
if (currentModeId) {
|
|
1010
|
-
const modeOption = findConfigOptionByCategory(configOptions, "mode");
|
|
1011
|
-
if (modeOption) {
|
|
1012
|
-
configOptions = applyConfigOptionValue(configOptions, modeOption.id, currentModeId) ?? configOptions;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
const updated = {
|
|
1016
|
-
...record,
|
|
1017
|
-
configOptions,
|
|
1018
|
-
modes: deriveModesFromConfigOptions(configOptions) ?? record.modes
|
|
1019
|
-
};
|
|
1020
|
-
await this.persist.updateSession(updated);
|
|
1021
|
-
return updated;
|
|
1022
|
-
}
|
|
1023
|
-
async rawSendSessionMethod(sessionId, method, params, options = {}) {
|
|
1024
|
-
return this.sendSessionMethodInternal(sessionId, method, params, options, false);
|
|
1025
|
-
}
|
|
1026
|
-
async sendSessionMethodInternal(sessionId, method, params, options, allowManagedCancel) {
|
|
1027
|
-
if (method === SESSION_CANCEL_METHOD && !allowManagedCancel) {
|
|
1028
|
-
throw new Error(MANUAL_CANCEL_ERROR);
|
|
1029
|
-
}
|
|
1030
|
-
const record = await this.persist.getSession(sessionId);
|
|
1031
|
-
if (!record) {
|
|
1032
|
-
throw new Error(`session '${sessionId}' not found`);
|
|
1033
|
-
}
|
|
1034
|
-
const live = await this.getLiveConnection(record.agent);
|
|
1035
|
-
if (!live.hasBoundSession(record.id, record.agentSessionId)) {
|
|
1036
|
-
const restored = await this.resumeSession(record.id);
|
|
1037
|
-
return this.sendSessionMethodInternal(restored.id, method, params, options, allowManagedCancel);
|
|
1038
|
-
}
|
|
1039
|
-
const response = await live.sendSessionMethod(record.id, method, params, options);
|
|
1040
|
-
await this.persistSessionStateFromMethod(record.id, method, params, response);
|
|
1041
|
-
const refreshed = await this.requireSessionRecord(record.id);
|
|
1042
|
-
return {
|
|
1043
|
-
session: this.upsertSessionHandle(refreshed),
|
|
1044
|
-
response
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
async persistSessionStateFromMethod(sessionId, method, params, response) {
|
|
1048
|
-
const record = await this.persist.getSession(sessionId);
|
|
1049
|
-
if (!record) {
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (method === "session/set_config_option") {
|
|
1053
|
-
const configId = typeof params.configId === "string" ? params.configId : null;
|
|
1054
|
-
const value = typeof params.value === "string" ? params.value : null;
|
|
1055
|
-
const updates = {};
|
|
1056
|
-
const serverConfigOptions = extractConfigOptionsFromSetResponse(response);
|
|
1057
|
-
if (serverConfigOptions) {
|
|
1058
|
-
updates.configOptions = cloneConfigOptions(serverConfigOptions);
|
|
1059
|
-
} else if (record.configOptions && configId && value) {
|
|
1060
|
-
const updated = applyConfigOptionValue(record.configOptions, configId, value);
|
|
1061
|
-
if (updated) {
|
|
1062
|
-
updates.configOptions = updated;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
if (configId && value) {
|
|
1066
|
-
const source = updates.configOptions ?? record.configOptions;
|
|
1067
|
-
const option = source ? findConfigOptionById(source, configId) : null;
|
|
1068
|
-
if (option?.category === "mode") {
|
|
1069
|
-
const nextModes = applyCurrentMode(record.modes, value);
|
|
1070
|
-
if (nextModes) {
|
|
1071
|
-
updates.modes = nextModes;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
if (Object.keys(updates).length > 0) {
|
|
1076
|
-
await this.persist.updateSession({ ...record, ...updates });
|
|
1077
|
-
}
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
if (method === "session/set_mode") {
|
|
1081
|
-
const modeId = typeof params.modeId === "string" ? params.modeId : null;
|
|
1082
|
-
if (!modeId) {
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
const updates = {};
|
|
1086
|
-
const nextModes = applyCurrentMode(record.modes, modeId);
|
|
1087
|
-
if (nextModes) {
|
|
1088
|
-
updates.modes = nextModes;
|
|
1089
|
-
}
|
|
1090
|
-
if (record.configOptions) {
|
|
1091
|
-
const modeOption = findConfigOptionByCategory(record.configOptions, "mode");
|
|
1092
|
-
if (modeOption) {
|
|
1093
|
-
const updated = applyConfigOptionValue(record.configOptions, modeOption.id, modeId);
|
|
1094
|
-
if (updated) {
|
|
1095
|
-
updates.configOptions = updated;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
if (Object.keys(updates).length > 0) {
|
|
1100
|
-
await this.persist.updateSession({ ...record, ...updates });
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
onSessionEvent(sessionId, listener) {
|
|
1105
|
-
const listeners = this.eventListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
1106
|
-
listeners.add(listener);
|
|
1107
|
-
this.eventListeners.set(sessionId, listeners);
|
|
1108
|
-
return () => {
|
|
1109
|
-
const set = this.eventListeners.get(sessionId);
|
|
1110
|
-
if (!set) {
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
set.delete(listener);
|
|
1114
|
-
if (set.size === 0) {
|
|
1115
|
-
this.eventListeners.delete(sessionId);
|
|
1116
|
-
}
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
onPermissionRequest(sessionId, listener) {
|
|
1120
|
-
const listeners = this.permissionListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
1121
|
-
listeners.add(listener);
|
|
1122
|
-
this.permissionListeners.set(sessionId, listeners);
|
|
1123
|
-
return () => {
|
|
1124
|
-
const set = this.permissionListeners.get(sessionId);
|
|
1125
|
-
if (!set) {
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
|
-
set.delete(listener);
|
|
1129
|
-
if (set.size === 0) {
|
|
1130
|
-
this.permissionListeners.delete(sessionId);
|
|
1131
|
-
}
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
async respondPermission(permissionId, reply) {
|
|
1135
|
-
const pending = this.pendingPermissionRequests.get(permissionId);
|
|
1136
|
-
if (!pending) {
|
|
1137
|
-
throw new Error(`permission '${permissionId}' not found`);
|
|
1138
|
-
}
|
|
1139
|
-
let response;
|
|
1140
|
-
try {
|
|
1141
|
-
response = permissionReplyToResponse(permissionId, pending.request, reply);
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
1144
|
-
this.pendingPermissionRequests.delete(permissionId);
|
|
1145
|
-
throw error;
|
|
1146
|
-
}
|
|
1147
|
-
this.resolvePendingPermission(permissionId, response);
|
|
1148
|
-
}
|
|
1149
|
-
async rawRespondPermission(permissionId, response) {
|
|
1150
|
-
if (!this.pendingPermissionRequests.has(permissionId)) {
|
|
1151
|
-
throw new Error(`permission '${permissionId}' not found`);
|
|
1152
|
-
}
|
|
1153
|
-
this.resolvePendingPermission(permissionId, clonePermissionResponse(response));
|
|
1154
|
-
}
|
|
1155
|
-
async getHealth() {
|
|
1156
|
-
return this.requestHealth();
|
|
1157
|
-
}
|
|
1158
|
-
async listAgents(options) {
|
|
1159
|
-
return this.requestJson("GET", `${API_PREFIX}/agents`, {
|
|
1160
|
-
query: toAgentQuery(options)
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
async getAgent(agent, options) {
|
|
1164
|
-
try {
|
|
1165
|
-
return await this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
|
|
1166
|
-
query: toAgentQuery(options)
|
|
1167
|
-
});
|
|
1168
|
-
} catch (error) {
|
|
1169
|
-
if (!(error instanceof SandboxAgentError) || error.status !== 404) {
|
|
1170
|
-
throw error;
|
|
1171
|
-
}
|
|
1172
|
-
const listed = await this.listAgents(options);
|
|
1173
|
-
const match = listed.agents.find((entry) => entry.id === agent);
|
|
1174
|
-
if (match) {
|
|
1175
|
-
return match;
|
|
1176
|
-
}
|
|
1177
|
-
throw error;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
async installAgent(agent, request = {}) {
|
|
1181
|
-
return this.requestJson("POST", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/install`, {
|
|
1182
|
-
body: request
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
async listAcpServers() {
|
|
1186
|
-
return this.requestJson("GET", `${API_PREFIX}/acp`);
|
|
1187
|
-
}
|
|
1188
|
-
async listFsEntries(query = {}) {
|
|
1189
|
-
return this.requestJson("GET", `${FS_PATH}/entries`, {
|
|
1190
|
-
query
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
async readFsFile(query) {
|
|
1194
|
-
const response = await this.requestRaw("GET", `${FS_PATH}/file`, {
|
|
1195
|
-
query,
|
|
1196
|
-
accept: "application/octet-stream"
|
|
1197
|
-
});
|
|
1198
|
-
const buffer = await response.arrayBuffer();
|
|
1199
|
-
return new Uint8Array(buffer);
|
|
1200
|
-
}
|
|
1201
|
-
async writeFsFile(query, body) {
|
|
1202
|
-
const response = await this.requestRaw("PUT", `${FS_PATH}/file`, {
|
|
1203
|
-
query,
|
|
1204
|
-
rawBody: body,
|
|
1205
|
-
contentType: "application/octet-stream",
|
|
1206
|
-
accept: "application/json"
|
|
1207
|
-
});
|
|
1208
|
-
return await response.json();
|
|
1209
|
-
}
|
|
1210
|
-
async deleteFsEntry(query) {
|
|
1211
|
-
return this.requestJson("DELETE", `${FS_PATH}/entry`, { query });
|
|
1212
|
-
}
|
|
1213
|
-
async mkdirFs(query) {
|
|
1214
|
-
return this.requestJson("POST", `${FS_PATH}/mkdir`, { query });
|
|
1215
|
-
}
|
|
1216
|
-
async moveFs(request) {
|
|
1217
|
-
return this.requestJson("POST", `${FS_PATH}/move`, { body: request });
|
|
1218
|
-
}
|
|
1219
|
-
async statFs(query) {
|
|
1220
|
-
return this.requestJson("GET", `${FS_PATH}/stat`, { query });
|
|
1221
|
-
}
|
|
1222
|
-
async uploadFsBatch(body, query) {
|
|
1223
|
-
const response = await this.requestRaw("POST", `${FS_PATH}/upload-batch`, {
|
|
1224
|
-
query,
|
|
1225
|
-
rawBody: body,
|
|
1226
|
-
contentType: "application/x-tar",
|
|
1227
|
-
accept: "application/json"
|
|
1228
|
-
});
|
|
1229
|
-
return await response.json();
|
|
1230
|
-
}
|
|
1231
|
-
async getMcpConfig(query) {
|
|
1232
|
-
return this.requestJson("GET", `${API_PREFIX}/config/mcp`, { query });
|
|
1233
|
-
}
|
|
1234
|
-
async setMcpConfig(query, config) {
|
|
1235
|
-
await this.requestRaw("PUT", `${API_PREFIX}/config/mcp`, { query, body: config });
|
|
1236
|
-
}
|
|
1237
|
-
async deleteMcpConfig(query) {
|
|
1238
|
-
await this.requestRaw("DELETE", `${API_PREFIX}/config/mcp`, { query });
|
|
1239
|
-
}
|
|
1240
|
-
async getSkillsConfig(query) {
|
|
1241
|
-
return this.requestJson("GET", `${API_PREFIX}/config/skills`, { query });
|
|
1242
|
-
}
|
|
1243
|
-
async setSkillsConfig(query, config) {
|
|
1244
|
-
await this.requestRaw("PUT", `${API_PREFIX}/config/skills`, { query, body: config });
|
|
1245
|
-
}
|
|
1246
|
-
async deleteSkillsConfig(query) {
|
|
1247
|
-
await this.requestRaw("DELETE", `${API_PREFIX}/config/skills`, { query });
|
|
1248
|
-
}
|
|
1249
|
-
async getProcessConfig() {
|
|
1250
|
-
return this.requestJson("GET", `${API_PREFIX}/processes/config`);
|
|
1251
|
-
}
|
|
1252
|
-
async setProcessConfig(config) {
|
|
1253
|
-
return this.requestJson("POST", `${API_PREFIX}/processes/config`, {
|
|
1254
|
-
body: config
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
async createProcess(request) {
|
|
1258
|
-
return this.requestJson("POST", `${API_PREFIX}/processes`, {
|
|
1259
|
-
body: request
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
async runProcess(request) {
|
|
1263
|
-
return this.requestJson("POST", `${API_PREFIX}/processes/run`, {
|
|
1264
|
-
body: request
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
async listProcesses() {
|
|
1268
|
-
return this.requestJson("GET", `${API_PREFIX}/processes`);
|
|
1269
|
-
}
|
|
1270
|
-
async getProcess(id) {
|
|
1271
|
-
return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
|
|
1272
|
-
}
|
|
1273
|
-
async stopProcess(id, query) {
|
|
1274
|
-
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/stop`, {
|
|
1275
|
-
query
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
async killProcess(id, query) {
|
|
1279
|
-
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/kill`, {
|
|
1280
|
-
query
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
async deleteProcess(id) {
|
|
1284
|
-
await this.requestRaw("DELETE", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
|
|
1285
|
-
}
|
|
1286
|
-
async getProcessLogs(id, query = {}) {
|
|
1287
|
-
return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`, {
|
|
1288
|
-
query
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
async followProcessLogs(id, listener, query = {}) {
|
|
1292
|
-
const abortController = new AbortController();
|
|
1293
|
-
const response = await this.requestRaw("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`, {
|
|
1294
|
-
query: { ...query, follow: true },
|
|
1295
|
-
accept: "text/event-stream",
|
|
1296
|
-
signal: abortController.signal
|
|
1297
|
-
});
|
|
1298
|
-
if (!response.body) {
|
|
1299
|
-
abortController.abort();
|
|
1300
|
-
throw new Error("SSE stream is not readable in this environment.");
|
|
1301
|
-
}
|
|
1302
|
-
const closed = consumeProcessLogSse(response.body, listener, abortController.signal);
|
|
1303
|
-
return {
|
|
1304
|
-
close: () => abortController.abort(),
|
|
1305
|
-
closed
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
async sendProcessInput(id, request) {
|
|
1309
|
-
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/input`, {
|
|
1310
|
-
body: request
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
async resizeProcessTerminal(id, request) {
|
|
1314
|
-
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/resize`, {
|
|
1315
|
-
body: request
|
|
1316
|
-
});
|
|
1317
|
-
}
|
|
1318
|
-
buildProcessTerminalWebSocketUrl(id, options = {}) {
|
|
1319
|
-
return toWebSocketUrl(
|
|
1320
|
-
this.buildUrl(`${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/ws`, {
|
|
1321
|
-
access_token: options.accessToken ?? this.token
|
|
1322
|
-
})
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
connectProcessTerminalWebSocket(id, options = {}) {
|
|
1326
|
-
const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket;
|
|
1327
|
-
if (!WebSocketCtor) {
|
|
1328
|
-
throw new Error("WebSocket API is not available; provide a WebSocket implementation.");
|
|
1329
|
-
}
|
|
1330
|
-
return new WebSocketCtor(
|
|
1331
|
-
this.buildProcessTerminalWebSocketUrl(id, {
|
|
1332
|
-
accessToken: options.accessToken
|
|
1333
|
-
}),
|
|
1334
|
-
options.protocols
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
connectProcessTerminal(id, options = {}) {
|
|
1338
|
-
return new ProcessTerminalSession(this.connectProcessTerminalWebSocket(id, options));
|
|
1339
|
-
}
|
|
1340
|
-
async getLiveConnection(agent) {
|
|
1341
|
-
await this.awaitHealthy();
|
|
1342
|
-
const existing = this.liveConnections.get(agent);
|
|
1343
|
-
if (existing) {
|
|
1344
|
-
return existing;
|
|
1345
|
-
}
|
|
1346
|
-
const pending = this.pendingLiveConnections.get(agent);
|
|
1347
|
-
if (pending) {
|
|
1348
|
-
return pending;
|
|
1349
|
-
}
|
|
1350
|
-
const creating = (async () => {
|
|
1351
|
-
const serverId = `sdk-${agent}-${randomId()}`;
|
|
1352
|
-
const created = await LiveAcpConnection.create({
|
|
1353
|
-
baseUrl: this.baseUrl,
|
|
1354
|
-
token: this.token,
|
|
1355
|
-
fetcher: this.fetcher,
|
|
1356
|
-
headers: this.defaultHeaders,
|
|
1357
|
-
agent,
|
|
1358
|
-
serverId,
|
|
1359
|
-
onObservedEnvelope: (connection, envelope, direction, localSessionId) => {
|
|
1360
|
-
void this.enqueueObservedEnvelopePersistence(connection, envelope, direction, localSessionId).catch((error) => {
|
|
1361
|
-
console.error("Failed to persist observed sandbox-agent envelope", error);
|
|
1362
|
-
});
|
|
1363
|
-
},
|
|
1364
|
-
onPermissionRequest: async (connection, localSessionId, agentSessionId, request) => this.enqueuePermissionRequest(connection, localSessionId, agentSessionId, request)
|
|
1365
|
-
});
|
|
1366
|
-
const raced = this.liveConnections.get(agent);
|
|
1367
|
-
if (raced) {
|
|
1368
|
-
await created.close();
|
|
1369
|
-
return raced;
|
|
1370
|
-
}
|
|
1371
|
-
this.liveConnections.set(agent, created);
|
|
1372
|
-
return created;
|
|
1373
|
-
})();
|
|
1374
|
-
this.pendingLiveConnections.set(agent, creating);
|
|
1375
|
-
try {
|
|
1376
|
-
return await creating;
|
|
1377
|
-
} finally {
|
|
1378
|
-
if (this.pendingLiveConnections.get(agent) === creating) {
|
|
1379
|
-
this.pendingLiveConnections.delete(agent);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
async persistObservedEnvelope(connection, envelope, direction, localSessionId) {
|
|
1384
|
-
if (!localSessionId) {
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
let event = null;
|
|
1388
|
-
for (let attempt = 0; attempt < MAX_EVENT_INDEX_INSERT_RETRIES; attempt += 1) {
|
|
1389
|
-
event = {
|
|
1390
|
-
id: randomId(),
|
|
1391
|
-
eventIndex: await this.allocateSessionEventIndex(localSessionId),
|
|
1392
|
-
sessionId: localSessionId,
|
|
1393
|
-
createdAt: nowMs(),
|
|
1394
|
-
connectionId: connection.connectionId,
|
|
1395
|
-
sender: direction === "outbound" ? "client" : "agent",
|
|
1396
|
-
payload: cloneEnvelope(envelope)
|
|
1397
|
-
};
|
|
1398
|
-
try {
|
|
1399
|
-
await this.persist.insertEvent(localSessionId, event);
|
|
1400
|
-
break;
|
|
1401
|
-
} catch (error) {
|
|
1402
|
-
if (!isSessionEventIndexConflict(error) || attempt === MAX_EVENT_INDEX_INSERT_RETRIES - 1) {
|
|
1403
|
-
throw error;
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
if (!event) {
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
await this.persistSessionStateFromEvent(localSessionId, envelope, direction);
|
|
1411
|
-
const listeners = this.eventListeners.get(localSessionId);
|
|
1412
|
-
if (!listeners || listeners.size === 0) {
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
for (const listener of listeners) {
|
|
1416
|
-
listener(event);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
async enqueueObservedEnvelopePersistence(connection, envelope, direction, localSessionId) {
|
|
1420
|
-
if (!localSessionId) {
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
const previous = this.pendingObservedEnvelopePersistenceBySession.get(localSessionId) ?? Promise.resolve();
|
|
1424
|
-
const current = previous.catch(() => {
|
|
1425
|
-
}).then(() => this.persistObservedEnvelope(connection, envelope, direction, localSessionId));
|
|
1426
|
-
this.pendingObservedEnvelopePersistenceBySession.set(localSessionId, current);
|
|
1427
|
-
try {
|
|
1428
|
-
await current;
|
|
1429
|
-
} finally {
|
|
1430
|
-
if (this.pendingObservedEnvelopePersistenceBySession.get(localSessionId) === current) {
|
|
1431
|
-
this.pendingObservedEnvelopePersistenceBySession.delete(localSessionId);
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
async persistSessionStateFromEvent(sessionId, envelope, direction) {
|
|
1436
|
-
if (direction !== "inbound") {
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
if (envelopeMethod(envelope) !== "session/update") {
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
const update = envelopeSessionUpdate(envelope);
|
|
1443
|
-
if (!update || typeof update.sessionUpdate !== "string") {
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
const record = await this.persist.getSession(sessionId);
|
|
1447
|
-
if (!record) {
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
if (update.sessionUpdate === "config_option_update") {
|
|
1451
|
-
const configOptions = normalizeSessionConfigOptions(update.configOptions);
|
|
1452
|
-
if (configOptions) {
|
|
1453
|
-
await this.persist.updateSession({
|
|
1454
|
-
...record,
|
|
1455
|
-
configOptions
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
if (update.sessionUpdate === "current_mode_update") {
|
|
1461
|
-
const modeId = typeof update.currentModeId === "string" ? update.currentModeId : null;
|
|
1462
|
-
if (!modeId) {
|
|
1463
|
-
return;
|
|
1464
|
-
}
|
|
1465
|
-
const nextModes = applyCurrentMode(record.modes, modeId);
|
|
1466
|
-
if (!nextModes) {
|
|
1467
|
-
return;
|
|
1468
|
-
}
|
|
1469
|
-
await this.persist.updateSession({
|
|
1470
|
-
...record,
|
|
1471
|
-
modes: nextModes
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
async allocateSessionEventIndex(sessionId) {
|
|
1476
|
-
await this.ensureSessionEventIndexSeeded(sessionId);
|
|
1477
|
-
const nextIndex = this.nextSessionEventIndexBySession.get(sessionId) ?? 1;
|
|
1478
|
-
this.nextSessionEventIndexBySession.set(sessionId, nextIndex + 1);
|
|
1479
|
-
return nextIndex;
|
|
1480
|
-
}
|
|
1481
|
-
async ensureSessionEventIndexSeeded(sessionId) {
|
|
1482
|
-
if (this.nextSessionEventIndexBySession.has(sessionId)) {
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
if (!this.seedSessionEventIndexBySession.has(sessionId)) {
|
|
1486
|
-
const pending2 = (async () => {
|
|
1487
|
-
const maxPersistedIndex = await this.findMaxPersistedSessionEventIndex(sessionId);
|
|
1488
|
-
this.nextSessionEventIndexBySession.set(sessionId, Math.max(1, maxPersistedIndex + 1));
|
|
1489
|
-
})().finally(() => {
|
|
1490
|
-
this.seedSessionEventIndexBySession.delete(sessionId);
|
|
1491
|
-
});
|
|
1492
|
-
this.seedSessionEventIndexBySession.set(sessionId, pending2);
|
|
1493
|
-
}
|
|
1494
|
-
const pending = this.seedSessionEventIndexBySession.get(sessionId);
|
|
1495
|
-
if (pending) {
|
|
1496
|
-
await pending;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
async findMaxPersistedSessionEventIndex(sessionId) {
|
|
1500
|
-
let maxIndex = 0;
|
|
1501
|
-
let eventCursor;
|
|
1502
|
-
while (true) {
|
|
1503
|
-
const eventsPage = await this.persist.listEvents({
|
|
1504
|
-
sessionId,
|
|
1505
|
-
cursor: eventCursor,
|
|
1506
|
-
limit: EVENT_INDEX_SCAN_EVENTS_LIMIT
|
|
1507
|
-
});
|
|
1508
|
-
for (const event of eventsPage.items) {
|
|
1509
|
-
if (Number.isFinite(event.eventIndex) && event.eventIndex > maxIndex) {
|
|
1510
|
-
maxIndex = Math.floor(event.eventIndex);
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
if (!eventsPage.nextCursor) {
|
|
1514
|
-
break;
|
|
1515
|
-
}
|
|
1516
|
-
eventCursor = eventsPage.nextCursor;
|
|
1517
|
-
}
|
|
1518
|
-
return maxIndex;
|
|
1519
|
-
}
|
|
1520
|
-
async collectReplayEvents(sessionId, maxEvents) {
|
|
1521
|
-
const all = [];
|
|
1522
|
-
let cursor;
|
|
1523
|
-
while (true) {
|
|
1524
|
-
const page = await this.persist.listEvents({
|
|
1525
|
-
sessionId,
|
|
1526
|
-
cursor,
|
|
1527
|
-
limit: Math.max(100, maxEvents)
|
|
1528
|
-
});
|
|
1529
|
-
all.push(...page.items);
|
|
1530
|
-
if (!page.nextCursor) {
|
|
1531
|
-
break;
|
|
1532
|
-
}
|
|
1533
|
-
cursor = page.nextCursor;
|
|
1534
|
-
}
|
|
1535
|
-
return all.slice(-maxEvents);
|
|
1536
|
-
}
|
|
1537
|
-
upsertSessionHandle(record) {
|
|
1538
|
-
const existing = this.sessionHandles.get(record.id);
|
|
1539
|
-
if (existing) {
|
|
1540
|
-
existing.apply(record);
|
|
1541
|
-
return existing;
|
|
1542
|
-
}
|
|
1543
|
-
const created = new Session(this, record);
|
|
1544
|
-
this.sessionHandles.set(record.id, created);
|
|
1545
|
-
return created;
|
|
1546
|
-
}
|
|
1547
|
-
async requireSessionRecord(id) {
|
|
1548
|
-
const record = await this.persist.getSession(id);
|
|
1549
|
-
if (!record) {
|
|
1550
|
-
throw new Error(`session '${id}' not found`);
|
|
1551
|
-
}
|
|
1552
|
-
return record;
|
|
1553
|
-
}
|
|
1554
|
-
async enqueuePermissionRequest(_connection, localSessionId, agentSessionId, request) {
|
|
1555
|
-
const listeners = this.permissionListeners.get(localSessionId);
|
|
1556
|
-
if (!listeners || listeners.size === 0) {
|
|
1557
|
-
return cancelledPermissionResponse();
|
|
1558
|
-
}
|
|
1559
|
-
const pendingId = randomId();
|
|
1560
|
-
const permissionRequest = {
|
|
1561
|
-
id: pendingId,
|
|
1562
|
-
createdAt: nowMs(),
|
|
1563
|
-
sessionId: localSessionId,
|
|
1564
|
-
agentSessionId,
|
|
1565
|
-
availableReplies: availablePermissionReplies(request.options),
|
|
1566
|
-
options: request.options.map(clonePermissionOption),
|
|
1567
|
-
toolCall: clonePermissionToolCall(request.toolCall),
|
|
1568
|
-
rawRequest: clonePermissionRequest(request)
|
|
1569
|
-
};
|
|
1570
|
-
return await new Promise((resolve, reject) => {
|
|
1571
|
-
this.pendingPermissionRequests.set(pendingId, {
|
|
1572
|
-
id: pendingId,
|
|
1573
|
-
sessionId: localSessionId,
|
|
1574
|
-
request: clonePermissionRequest(request),
|
|
1575
|
-
resolve,
|
|
1576
|
-
reject
|
|
1577
|
-
});
|
|
1578
|
-
try {
|
|
1579
|
-
for (const listener of listeners) {
|
|
1580
|
-
listener(permissionRequest);
|
|
1581
|
-
}
|
|
1582
|
-
} catch (error) {
|
|
1583
|
-
this.pendingPermissionRequests.delete(pendingId);
|
|
1584
|
-
reject(error);
|
|
1585
|
-
}
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
|
-
resolvePendingPermission(permissionId, response) {
|
|
1589
|
-
const pending = this.pendingPermissionRequests.get(permissionId);
|
|
1590
|
-
if (!pending) {
|
|
1591
|
-
throw new Error(`permission '${permissionId}' not found`);
|
|
1592
|
-
}
|
|
1593
|
-
this.pendingPermissionRequests.delete(permissionId);
|
|
1594
|
-
pending.resolve(response);
|
|
1595
|
-
}
|
|
1596
|
-
cancelPendingPermissionsForSession(sessionId) {
|
|
1597
|
-
for (const [permissionId, pending] of this.pendingPermissionRequests) {
|
|
1598
|
-
if (pending.sessionId !== sessionId) {
|
|
1599
|
-
continue;
|
|
1600
|
-
}
|
|
1601
|
-
this.pendingPermissionRequests.delete(permissionId);
|
|
1602
|
-
pending.resolve(cancelledPermissionResponse());
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
async requestJson(method, path, options = {}) {
|
|
1606
|
-
const response = await this.requestRaw(method, path, {
|
|
1607
|
-
query: options.query,
|
|
1608
|
-
body: options.body,
|
|
1609
|
-
headers: options.headers,
|
|
1610
|
-
accept: options.accept ?? "application/json",
|
|
1611
|
-
signal: options.signal,
|
|
1612
|
-
skipReadyWait: options.skipReadyWait
|
|
1613
|
-
});
|
|
1614
|
-
if (response.status === 204) {
|
|
1615
|
-
return void 0;
|
|
1616
|
-
}
|
|
1617
|
-
return await response.json();
|
|
1618
|
-
}
|
|
1619
|
-
async requestRaw(method, path, options = {}) {
|
|
1620
|
-
if (!options.skipReadyWait) {
|
|
1621
|
-
await this.awaitHealthy(options.signal);
|
|
1622
|
-
}
|
|
1623
|
-
const url = this.buildUrl(path, options.query);
|
|
1624
|
-
const headers = this.buildHeaders(options.headers);
|
|
1625
|
-
if (options.accept) {
|
|
1626
|
-
headers.set("Accept", options.accept);
|
|
1627
|
-
}
|
|
1628
|
-
const init = {
|
|
1629
|
-
method,
|
|
1630
|
-
headers,
|
|
1631
|
-
signal: options.signal
|
|
1632
|
-
};
|
|
1633
|
-
if (options.rawBody !== void 0 && options.body !== void 0) {
|
|
1634
|
-
throw new Error("requestRaw received both rawBody and body");
|
|
1635
|
-
}
|
|
1636
|
-
if (options.rawBody !== void 0) {
|
|
1637
|
-
if (options.contentType) {
|
|
1638
|
-
headers.set("Content-Type", options.contentType);
|
|
1639
|
-
}
|
|
1640
|
-
init.body = options.rawBody;
|
|
1641
|
-
} else if (options.body !== void 0) {
|
|
1642
|
-
headers.set("Content-Type", "application/json");
|
|
1643
|
-
init.body = JSON.stringify(options.body);
|
|
1644
|
-
}
|
|
1645
|
-
const response = await this.fetcher(url, init);
|
|
1646
|
-
if (!response.ok) {
|
|
1647
|
-
const problem = await readProblem(response);
|
|
1648
|
-
throw new SandboxAgentError(response.status, problem, response);
|
|
1649
|
-
}
|
|
1650
|
-
return response;
|
|
1651
|
-
}
|
|
1652
|
-
startHealthWait() {
|
|
1653
|
-
if (!this.healthWait.enabled || this.healthPromise) {
|
|
1654
|
-
return;
|
|
1655
|
-
}
|
|
1656
|
-
this.healthPromise = this.runHealthWait().catch((error) => {
|
|
1657
|
-
this.healthError = error instanceof Error ? error : new Error(String(error));
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
async awaitHealthy(signal) {
|
|
1661
|
-
if (!this.healthPromise) {
|
|
1662
|
-
throwIfAborted(signal);
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
await waitForAbortable(this.healthPromise, signal);
|
|
1666
|
-
throwIfAborted(signal);
|
|
1667
|
-
if (this.healthError) {
|
|
1668
|
-
throw this.healthError;
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
async runHealthWait() {
|
|
1672
|
-
const signal = this.healthWait.enabled ? anyAbortSignal([this.healthWait.signal, this.healthWaitAbortController.signal]) : void 0;
|
|
1673
|
-
const startedAt = Date.now();
|
|
1674
|
-
const deadline = typeof this.healthWait.timeoutMs === "number" ? startedAt + this.healthWait.timeoutMs : void 0;
|
|
1675
|
-
let delayMs = HEALTH_WAIT_MIN_DELAY_MS;
|
|
1676
|
-
let nextLogAt = startedAt + HEALTH_WAIT_LOG_AFTER_MS;
|
|
1677
|
-
let lastError;
|
|
1678
|
-
let consecutiveFailures = 0;
|
|
1679
|
-
while (!this.disposed && (deadline === void 0 || Date.now() < deadline)) {
|
|
1680
|
-
throwIfAborted(signal);
|
|
1681
|
-
try {
|
|
1682
|
-
const health = await this.requestHealth({ signal });
|
|
1683
|
-
if (health.status === "ok") {
|
|
1684
|
-
return;
|
|
1685
|
-
}
|
|
1686
|
-
lastError = new Error(`Unexpected health response: ${JSON.stringify(health)}`);
|
|
1687
|
-
consecutiveFailures++;
|
|
1688
|
-
} catch (error) {
|
|
1689
|
-
if (isAbortError(error)) {
|
|
1690
|
-
throw error;
|
|
1691
|
-
}
|
|
1692
|
-
lastError = error;
|
|
1693
|
-
consecutiveFailures++;
|
|
1694
|
-
}
|
|
1695
|
-
if (consecutiveFailures >= HEALTH_WAIT_ENSURE_SERVER_AFTER_FAILURES && this.sandboxProvider?.ensureServer && this.sandboxProviderRawId) {
|
|
1696
|
-
try {
|
|
1697
|
-
await this.sandboxProvider.ensureServer(this.sandboxProviderRawId);
|
|
1698
|
-
} catch {
|
|
1699
|
-
}
|
|
1700
|
-
consecutiveFailures = 0;
|
|
1701
|
-
}
|
|
1702
|
-
const now = Date.now();
|
|
1703
|
-
if (now >= nextLogAt) {
|
|
1704
|
-
const details = formatHealthWaitError(lastError);
|
|
1705
|
-
console.warn(`sandbox-agent at ${this.baseUrl} is not healthy after ${now - startedAt}ms; still waiting (${details})`);
|
|
1706
|
-
nextLogAt = now + HEALTH_WAIT_LOG_EVERY_MS;
|
|
1707
|
-
}
|
|
1708
|
-
await sleep(delayMs, signal);
|
|
1709
|
-
delayMs = Math.min(HEALTH_WAIT_MAX_DELAY_MS, delayMs * 2);
|
|
1710
|
-
}
|
|
1711
|
-
if (this.disposed) {
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
throw new Error(`Timed out waiting for sandbox-agent health after ${this.healthWait.timeoutMs}ms (${formatHealthWaitError(lastError)})`);
|
|
1715
|
-
}
|
|
1716
|
-
buildHeaders(extra) {
|
|
1717
|
-
const headers = new Headers(this.defaultHeaders ?? void 0);
|
|
1718
|
-
if (this.token) {
|
|
1719
|
-
headers.set("Authorization", `Bearer ${this.token}`);
|
|
1720
|
-
}
|
|
1721
|
-
if (extra) {
|
|
1722
|
-
const merged = new Headers(extra);
|
|
1723
|
-
merged.forEach((value, key) => headers.set(key, value));
|
|
1724
|
-
}
|
|
1725
|
-
return headers;
|
|
1726
|
-
}
|
|
1727
|
-
buildUrl(path, query) {
|
|
1728
|
-
const url = new URL(`${this.baseUrl}${path}`);
|
|
1729
|
-
if (query) {
|
|
1730
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
1731
|
-
if (value === void 0 || value === null) {
|
|
1732
|
-
return;
|
|
1733
|
-
}
|
|
1734
|
-
url.searchParams.set(key, String(value));
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
return url.toString();
|
|
1738
|
-
}
|
|
1739
|
-
async requestHealth(options = {}) {
|
|
1740
|
-
return this.requestJson("GET", `${API_PREFIX}/health`, {
|
|
1741
|
-
signal: options.signal,
|
|
1742
|
-
skipReadyWait: true
|
|
1743
|
-
});
|
|
1744
|
-
}
|
|
1745
|
-
};
|
|
1746
|
-
function isSessionEventIndexConflict(error) {
|
|
1747
|
-
if (!(error instanceof Error)) {
|
|
1748
|
-
return false;
|
|
1749
|
-
}
|
|
1750
|
-
return /UNIQUE constraint failed: .*session_id, .*event_index/.test(error.message);
|
|
1751
|
-
}
|
|
1752
|
-
function parseProcessTerminalServerFrame(payload) {
|
|
1753
|
-
try {
|
|
1754
|
-
const parsed = JSON.parse(payload);
|
|
1755
|
-
if (!isRecord(parsed) || typeof parsed.type !== "string") {
|
|
1756
|
-
return null;
|
|
1757
|
-
}
|
|
1758
|
-
if (parsed.type === "ready" && typeof parsed.processId === "string") {
|
|
1759
|
-
return parsed;
|
|
1760
|
-
}
|
|
1761
|
-
if (parsed.type === "exit" && (parsed.exitCode === void 0 || parsed.exitCode === null || typeof parsed.exitCode === "number")) {
|
|
1762
|
-
return parsed;
|
|
1763
|
-
}
|
|
1764
|
-
if (parsed.type === "error" && typeof parsed.message === "string") {
|
|
1765
|
-
return parsed;
|
|
1766
|
-
}
|
|
1767
|
-
} catch {
|
|
1768
|
-
return null;
|
|
1769
|
-
}
|
|
1770
|
-
return null;
|
|
1771
|
-
}
|
|
1772
|
-
function encodeTerminalInput(data) {
|
|
1773
|
-
if (typeof data === "string") {
|
|
1774
|
-
return { data };
|
|
1775
|
-
}
|
|
1776
|
-
const bytes = encodeTerminalBytes(data);
|
|
1777
|
-
return {
|
|
1778
|
-
data: bytesToBase64(bytes),
|
|
1779
|
-
encoding: "base64"
|
|
1780
|
-
};
|
|
1781
|
-
}
|
|
1782
|
-
function encodeTerminalBytes(data) {
|
|
1783
|
-
if (data instanceof ArrayBuffer) {
|
|
1784
|
-
return new Uint8Array(data);
|
|
1785
|
-
}
|
|
1786
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
|
|
1787
|
-
}
|
|
1788
|
-
async function decodeTerminalBytes(data) {
|
|
1789
|
-
if (data instanceof ArrayBuffer) {
|
|
1790
|
-
return new Uint8Array(data);
|
|
1791
|
-
}
|
|
1792
|
-
if (ArrayBuffer.isView(data)) {
|
|
1793
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
|
|
1794
|
-
}
|
|
1795
|
-
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
1796
|
-
return new Uint8Array(await data.arrayBuffer());
|
|
1797
|
-
}
|
|
1798
|
-
throw new Error(`Unsupported terminal frame payload: ${String(data)}`);
|
|
1799
|
-
}
|
|
1800
|
-
function bytesToBase64(bytes) {
|
|
1801
|
-
if (typeof Buffer !== "undefined") {
|
|
1802
|
-
return Buffer.from(bytes).toString("base64");
|
|
1803
|
-
}
|
|
1804
|
-
if (typeof btoa === "function") {
|
|
1805
|
-
let binary = "";
|
|
1806
|
-
const chunkSize = 32768;
|
|
1807
|
-
for (let index = 0; index < bytes.length; index += chunkSize) {
|
|
1808
|
-
binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
|
|
1809
|
-
}
|
|
1810
|
-
return btoa(binary);
|
|
1811
|
-
}
|
|
1812
|
-
throw new Error("Base64 encoding is not available in this environment.");
|
|
1813
|
-
}
|
|
1814
|
-
async function autoAuthenticate(acp, methods) {
|
|
1815
|
-
const envBased = methods.find((m) => m.id === "codex-api-key" || m.id === "openai-api-key" || m.id === "anthropic-api-key");
|
|
1816
|
-
if (!envBased) {
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
try {
|
|
1820
|
-
await acp.authenticate({ methodId: envBased.id });
|
|
1821
|
-
} catch {
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
function toAgentQuery(options) {
|
|
1825
|
-
if (!options) {
|
|
1826
|
-
return void 0;
|
|
1827
|
-
}
|
|
1828
|
-
return {
|
|
1829
|
-
config: options.config,
|
|
1830
|
-
no_cache: options.noCache
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
function normalizeSessionInit(value, cwdShorthand) {
|
|
1834
|
-
if (!value) {
|
|
1835
|
-
return {
|
|
1836
|
-
cwd: cwdShorthand ?? defaultCwd(),
|
|
1837
|
-
mcpServers: []
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
return {
|
|
1841
|
-
...value,
|
|
1842
|
-
cwd: value.cwd ?? cwdShorthand ?? defaultCwd(),
|
|
1843
|
-
mcpServers: value.mcpServers ?? []
|
|
1844
|
-
};
|
|
1845
|
-
}
|
|
1846
|
-
function mapSessionParams(params, agentSessionId) {
|
|
1847
|
-
return {
|
|
1848
|
-
...params,
|
|
1849
|
-
sessionId: agentSessionId
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
function injectReplayPrompt(params, replayText) {
|
|
1853
|
-
const prompt = Array.isArray(params.prompt) ? [...params.prompt] : [];
|
|
1854
|
-
prompt.unshift({
|
|
1855
|
-
type: "text",
|
|
1856
|
-
text: replayText
|
|
1857
|
-
});
|
|
1858
|
-
params.prompt = prompt;
|
|
1859
|
-
}
|
|
1860
|
-
function buildReplayText(events, maxChars) {
|
|
1861
|
-
if (events.length === 0) {
|
|
1862
|
-
return null;
|
|
1863
|
-
}
|
|
1864
|
-
const prefix = "Previous session history is replayed below as JSON-RPC envelopes. Use it as context before responding to the latest user prompt.\n";
|
|
1865
|
-
let text = prefix;
|
|
1866
|
-
for (const event of events) {
|
|
1867
|
-
const line = JSON.stringify({
|
|
1868
|
-
createdAt: event.createdAt,
|
|
1869
|
-
sender: event.sender,
|
|
1870
|
-
payload: event.payload
|
|
1871
|
-
});
|
|
1872
|
-
if (text.length + line.length + 1 > maxChars) {
|
|
1873
|
-
text += "\n[history truncated]";
|
|
1874
|
-
break;
|
|
1875
|
-
}
|
|
1876
|
-
text += `${line}
|
|
1877
|
-
`;
|
|
1878
|
-
}
|
|
1879
|
-
return text;
|
|
1880
|
-
}
|
|
1881
|
-
function envelopeMethod(message) {
|
|
1882
|
-
if (!isRecord(message) || !("method" in message) || typeof message["method"] !== "string") {
|
|
1883
|
-
return null;
|
|
1884
|
-
}
|
|
1885
|
-
return message["method"];
|
|
1886
|
-
}
|
|
1887
|
-
function envelopeId(message) {
|
|
1888
|
-
if (!isRecord(message) || !("id" in message) || message["id"] === void 0 || message["id"] === null) {
|
|
1889
|
-
return null;
|
|
1890
|
-
}
|
|
1891
|
-
return String(message["id"]);
|
|
1892
|
-
}
|
|
1893
|
-
function envelopeSessionIdFromParams(message) {
|
|
1894
|
-
if (!isRecord(message) || !("params" in message) || !isRecord(message["params"])) {
|
|
1895
|
-
return null;
|
|
1896
|
-
}
|
|
1897
|
-
const params = message["params"];
|
|
1898
|
-
if (typeof params.sessionId === "string" && params.sessionId.length > 0) {
|
|
1899
|
-
return params.sessionId;
|
|
1900
|
-
}
|
|
1901
|
-
return null;
|
|
1902
|
-
}
|
|
1903
|
-
function envelopeSessionIdFromResult(message) {
|
|
1904
|
-
if (!isRecord(message) || !("result" in message) || !isRecord(message["result"])) {
|
|
1905
|
-
return null;
|
|
1906
|
-
}
|
|
1907
|
-
const result = message["result"];
|
|
1908
|
-
if (typeof result.sessionId === "string" && result.sessionId.length > 0) {
|
|
1909
|
-
return result.sessionId;
|
|
1910
|
-
}
|
|
1911
|
-
return null;
|
|
1912
|
-
}
|
|
1913
|
-
function cloneEnvelope(envelope) {
|
|
1914
|
-
return JSON.parse(JSON.stringify(envelope));
|
|
1915
|
-
}
|
|
1916
|
-
function clonePermissionRequest(request) {
|
|
1917
|
-
return JSON.parse(JSON.stringify(request));
|
|
1918
|
-
}
|
|
1919
|
-
function clonePermissionResponse(response) {
|
|
1920
|
-
return JSON.parse(JSON.stringify(response));
|
|
1921
|
-
}
|
|
1922
|
-
function clonePermissionOption(option) {
|
|
1923
|
-
return {
|
|
1924
|
-
optionId: option.optionId,
|
|
1925
|
-
name: option.name,
|
|
1926
|
-
kind: option.kind
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
function clonePermissionToolCall(toolCall) {
|
|
1930
|
-
return JSON.parse(JSON.stringify(toolCall));
|
|
1931
|
-
}
|
|
1932
|
-
function isRecord(value) {
|
|
1933
|
-
return typeof value === "object" && value !== null;
|
|
1934
|
-
}
|
|
1935
|
-
function randomId() {
|
|
1936
|
-
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
1937
|
-
return globalThis.crypto.randomUUID();
|
|
1938
|
-
}
|
|
1939
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1940
|
-
}
|
|
1941
|
-
function nowMs() {
|
|
1942
|
-
return Date.now();
|
|
1943
|
-
}
|
|
1944
|
-
function defaultCwd() {
|
|
1945
|
-
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
1946
|
-
return process.cwd();
|
|
1947
|
-
}
|
|
1948
|
-
return "/";
|
|
1949
|
-
}
|
|
1950
|
-
function normalizePositiveInt(value, fallback) {
|
|
1951
|
-
if (!Number.isFinite(value) || (value ?? 0) < 1) {
|
|
1952
|
-
return fallback;
|
|
1953
|
-
}
|
|
1954
|
-
return Math.floor(value);
|
|
1955
|
-
}
|
|
1956
|
-
function normalizeHealthWaitOptions(skipHealthCheck, waitForHealth, signal) {
|
|
1957
|
-
if (skipHealthCheck === true || waitForHealth === false) {
|
|
1958
|
-
return { enabled: false };
|
|
1959
|
-
}
|
|
1960
|
-
if (waitForHealth === true || waitForHealth === void 0) {
|
|
1961
|
-
return { enabled: true, signal };
|
|
1962
|
-
}
|
|
1963
|
-
const timeoutMs = typeof waitForHealth.timeoutMs === "number" && Number.isFinite(waitForHealth.timeoutMs) && waitForHealth.timeoutMs > 0 ? Math.floor(waitForHealth.timeoutMs) : void 0;
|
|
1964
|
-
return {
|
|
1965
|
-
enabled: true,
|
|
1966
|
-
signal,
|
|
1967
|
-
timeoutMs
|
|
1968
|
-
};
|
|
1969
|
-
}
|
|
1970
|
-
function parseSandboxProviderId(sandboxId) {
|
|
1971
|
-
const slashIndex = sandboxId.indexOf("/");
|
|
1972
|
-
if (slashIndex < 1 || slashIndex === sandboxId.length - 1) {
|
|
1973
|
-
throw new Error(`Sandbox IDs must be prefixed as "{provider}/{id}". Received '${sandboxId}'.`);
|
|
1974
|
-
}
|
|
1975
|
-
return {
|
|
1976
|
-
provider: sandboxId.slice(0, slashIndex),
|
|
1977
|
-
rawId: sandboxId.slice(slashIndex + 1)
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
function requireSandboxBaseUrl(baseUrl, providerName) {
|
|
1981
|
-
if (!baseUrl) {
|
|
1982
|
-
throw new Error(`Sandbox provider '${providerName}' did not return a base URL.`);
|
|
1983
|
-
}
|
|
1984
|
-
return baseUrl;
|
|
1985
|
-
}
|
|
1986
|
-
async function resolveProviderFetch(provider, rawSandboxId) {
|
|
1987
|
-
if (provider.getFetch) {
|
|
1988
|
-
return await provider.getFetch(rawSandboxId);
|
|
1989
|
-
}
|
|
1990
|
-
return void 0;
|
|
1991
|
-
}
|
|
1992
|
-
async function resolveProviderToken(provider, rawSandboxId) {
|
|
1993
|
-
const maybeGetToken = provider.getToken;
|
|
1994
|
-
if (typeof maybeGetToken !== "function") {
|
|
1995
|
-
return void 0;
|
|
1996
|
-
}
|
|
1997
|
-
const token = await maybeGetToken.call(provider, rawSandboxId);
|
|
1998
|
-
return typeof token === "string" && token ? token : void 0;
|
|
1999
|
-
}
|
|
2000
|
-
async function readProblem(response) {
|
|
2001
|
-
try {
|
|
2002
|
-
const text = await response.clone().text();
|
|
2003
|
-
if (!text) {
|
|
2004
|
-
return void 0;
|
|
2005
|
-
}
|
|
2006
|
-
return JSON.parse(text);
|
|
2007
|
-
} catch {
|
|
2008
|
-
return void 0;
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
function normalizeSessionConfigOptions(value) {
|
|
2012
|
-
if (!Array.isArray(value)) {
|
|
2013
|
-
return void 0;
|
|
2014
|
-
}
|
|
2015
|
-
const normalized = value.filter(isSessionConfigOption);
|
|
2016
|
-
return cloneConfigOptions(normalized) ?? [];
|
|
2017
|
-
}
|
|
2018
|
-
function extractConfigOptionsFromSetResponse(response) {
|
|
2019
|
-
if (!isRecord(response)) {
|
|
2020
|
-
return void 0;
|
|
2021
|
-
}
|
|
2022
|
-
return normalizeSessionConfigOptions(response.configOptions);
|
|
2023
|
-
}
|
|
2024
|
-
function findConfigOptionByCategory(options, category) {
|
|
2025
|
-
return options.find((option) => option.category === category);
|
|
2026
|
-
}
|
|
2027
|
-
function findConfigOptionById(options, configId) {
|
|
2028
|
-
return options.find((option) => option.id === configId);
|
|
2029
|
-
}
|
|
2030
|
-
function uniqueCategories(options) {
|
|
2031
|
-
return [...new Set(options.map((option) => option.category).filter((value) => !!value))].sort();
|
|
2032
|
-
}
|
|
2033
|
-
function extractConfigValues(option) {
|
|
2034
|
-
if (!isRecord(option) || option.type !== "select" || !Array.isArray(option.options)) {
|
|
2035
|
-
return [];
|
|
2036
|
-
}
|
|
2037
|
-
const values = [];
|
|
2038
|
-
for (const entry of option.options) {
|
|
2039
|
-
if (isRecord(entry) && typeof entry.value === "string") {
|
|
2040
|
-
values.push(entry.value);
|
|
2041
|
-
continue;
|
|
2042
|
-
}
|
|
2043
|
-
if (isRecord(entry) && Array.isArray(entry.options)) {
|
|
2044
|
-
for (const nested of entry.options) {
|
|
2045
|
-
if (isRecord(nested) && typeof nested.value === "string") {
|
|
2046
|
-
values.push(nested.value);
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
return [...new Set(values)];
|
|
2052
|
-
}
|
|
2053
|
-
function extractKnownModeIds(modes) {
|
|
2054
|
-
if (!modes || !Array.isArray(modes.availableModes)) {
|
|
2055
|
-
return [];
|
|
2056
|
-
}
|
|
2057
|
-
return modes.availableModes.map((mode) => typeof mode.id === "string" ? mode.id : null).filter((value) => !!value);
|
|
2058
|
-
}
|
|
2059
|
-
function deriveModesFromConfigOptions(configOptions) {
|
|
2060
|
-
if (!configOptions || configOptions.length === 0) {
|
|
2061
|
-
return null;
|
|
2062
|
-
}
|
|
2063
|
-
const modeOption = findConfigOptionByCategory(configOptions, "mode");
|
|
2064
|
-
if (!modeOption || modeOption.type !== "select" || !Array.isArray(modeOption.options)) {
|
|
2065
|
-
return null;
|
|
2066
|
-
}
|
|
2067
|
-
const availableModes = modeOption.options.flatMap((entry) => flattenConfigOptions(entry)).map((entry) => ({
|
|
2068
|
-
id: entry.value,
|
|
2069
|
-
name: entry.name,
|
|
2070
|
-
description: entry.description ?? null
|
|
2071
|
-
}));
|
|
2072
|
-
return {
|
|
2073
|
-
currentModeId: typeof modeOption.currentValue === "string" && modeOption.currentValue.length > 0 ? modeOption.currentValue : availableModes[0]?.id ?? "",
|
|
2074
|
-
availableModes
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
function applyCurrentMode(modes, currentModeId) {
|
|
2078
|
-
if (modes && Array.isArray(modes.availableModes)) {
|
|
2079
|
-
return {
|
|
2080
|
-
...modes,
|
|
2081
|
-
currentModeId
|
|
2082
|
-
};
|
|
2083
|
-
}
|
|
2084
|
-
return {
|
|
2085
|
-
currentModeId,
|
|
2086
|
-
availableModes: []
|
|
2087
|
-
};
|
|
2088
|
-
}
|
|
2089
|
-
function applyConfigOptionValue(configOptions, configId, value) {
|
|
2090
|
-
const idx = configOptions.findIndex((o) => o.id === configId);
|
|
2091
|
-
if (idx === -1) {
|
|
2092
|
-
return null;
|
|
2093
|
-
}
|
|
2094
|
-
const updated = cloneConfigOptions(configOptions) ?? [];
|
|
2095
|
-
updated[idx] = { ...updated[idx], currentValue: value };
|
|
2096
|
-
return updated;
|
|
2097
|
-
}
|
|
2098
|
-
function flattenConfigOptions(entry) {
|
|
2099
|
-
if (!isRecord(entry)) {
|
|
2100
|
-
return [];
|
|
2101
|
-
}
|
|
2102
|
-
if (typeof entry.value === "string" && typeof entry.name === "string") {
|
|
2103
|
-
return [
|
|
2104
|
-
{
|
|
2105
|
-
value: entry.value,
|
|
2106
|
-
name: entry.name,
|
|
2107
|
-
description: typeof entry.description === "string" ? entry.description : void 0
|
|
2108
|
-
}
|
|
2109
|
-
];
|
|
2110
|
-
}
|
|
2111
|
-
if (!Array.isArray(entry.options)) {
|
|
2112
|
-
return [];
|
|
2113
|
-
}
|
|
2114
|
-
return entry.options.flatMap((nested) => flattenConfigOptions(nested));
|
|
2115
|
-
}
|
|
2116
|
-
function envelopeSessionUpdate(message) {
|
|
2117
|
-
if (!isRecord(message) || !("params" in message) || !isRecord(message.params)) {
|
|
2118
|
-
return null;
|
|
2119
|
-
}
|
|
2120
|
-
if (!("update" in message.params) || !isRecord(message.params.update)) {
|
|
2121
|
-
return null;
|
|
2122
|
-
}
|
|
2123
|
-
return message.params.update;
|
|
2124
|
-
}
|
|
2125
|
-
function cloneConfigOptions(value) {
|
|
2126
|
-
if (!value) {
|
|
2127
|
-
return void 0;
|
|
2128
|
-
}
|
|
2129
|
-
return JSON.parse(JSON.stringify(value));
|
|
2130
|
-
}
|
|
2131
|
-
function cloneModes(value) {
|
|
2132
|
-
if (!value) {
|
|
2133
|
-
return null;
|
|
2134
|
-
}
|
|
2135
|
-
return JSON.parse(JSON.stringify(value));
|
|
2136
|
-
}
|
|
2137
|
-
function availablePermissionReplies(options) {
|
|
2138
|
-
const replies = /* @__PURE__ */ new Set();
|
|
2139
|
-
for (const option of options) {
|
|
2140
|
-
if (option.kind === "allow_once") {
|
|
2141
|
-
replies.add("once");
|
|
2142
|
-
} else if (option.kind === "allow_always") {
|
|
2143
|
-
replies.add("always");
|
|
2144
|
-
} else if (option.kind === "reject_once" || option.kind === "reject_always") {
|
|
2145
|
-
replies.add("reject");
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
return [...replies];
|
|
2149
|
-
}
|
|
2150
|
-
function permissionReplyToResponse(permissionId, request, reply) {
|
|
2151
|
-
const preferredKinds = reply === "once" ? ["allow_once"] : reply === "always" ? ["allow_always", "allow_once"] : ["reject_once", "reject_always"];
|
|
2152
|
-
const selected = preferredKinds.map((kind) => request.options.find((option) => option.kind === kind)).find((option) => Boolean(option));
|
|
2153
|
-
if (!selected) {
|
|
2154
|
-
throw new UnsupportedPermissionReplyError(permissionId, reply, availablePermissionReplies(request.options));
|
|
2155
|
-
}
|
|
2156
|
-
return {
|
|
2157
|
-
outcome: {
|
|
2158
|
-
outcome: "selected",
|
|
2159
|
-
optionId: selected.optionId
|
|
2160
|
-
}
|
|
2161
|
-
};
|
|
2162
|
-
}
|
|
2163
|
-
function cancelledPermissionResponse() {
|
|
2164
|
-
return {
|
|
2165
|
-
outcome: {
|
|
2166
|
-
outcome: "cancelled"
|
|
2167
|
-
}
|
|
2168
|
-
};
|
|
2169
|
-
}
|
|
2170
|
-
function isSessionConfigOption(value) {
|
|
2171
|
-
return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.type === "string";
|
|
2172
|
-
}
|
|
2173
|
-
function toTitleCase(input) {
|
|
2174
|
-
if (!input) {
|
|
2175
|
-
return "";
|
|
2176
|
-
}
|
|
2177
|
-
return input.split(/[_\s-]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("");
|
|
2178
|
-
}
|
|
2179
|
-
function formatHealthWaitError(error) {
|
|
2180
|
-
if (error instanceof Error && error.message) {
|
|
2181
|
-
return error.message;
|
|
2182
|
-
}
|
|
2183
|
-
if (error === void 0 || error === null) {
|
|
2184
|
-
return "unknown error";
|
|
2185
|
-
}
|
|
2186
|
-
return String(error);
|
|
2187
|
-
}
|
|
2188
|
-
function anyAbortSignal(signals) {
|
|
2189
|
-
const active = signals.filter((signal) => Boolean(signal));
|
|
2190
|
-
if (active.length === 0) {
|
|
2191
|
-
return void 0;
|
|
2192
|
-
}
|
|
2193
|
-
if (active.length === 1) {
|
|
2194
|
-
return active[0];
|
|
2195
|
-
}
|
|
2196
|
-
const controller = new AbortController();
|
|
2197
|
-
const onAbort = (event) => {
|
|
2198
|
-
cleanup();
|
|
2199
|
-
const signal = event.target;
|
|
2200
|
-
controller.abort(signal.reason ?? createAbortError());
|
|
2201
|
-
};
|
|
2202
|
-
const cleanup = () => {
|
|
2203
|
-
for (const signal of active) {
|
|
2204
|
-
signal.removeEventListener("abort", onAbort);
|
|
2205
|
-
}
|
|
2206
|
-
};
|
|
2207
|
-
for (const signal of active) {
|
|
2208
|
-
if (signal.aborted) {
|
|
2209
|
-
controller.abort(signal.reason ?? createAbortError());
|
|
2210
|
-
return controller.signal;
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
for (const signal of active) {
|
|
2214
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
2215
|
-
}
|
|
2216
|
-
return controller.signal;
|
|
2217
|
-
}
|
|
2218
|
-
function throwIfAborted(signal) {
|
|
2219
|
-
if (!signal?.aborted) {
|
|
2220
|
-
return;
|
|
2221
|
-
}
|
|
2222
|
-
throw signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason);
|
|
2223
|
-
}
|
|
2224
|
-
async function waitForAbortable(promise, signal) {
|
|
2225
|
-
if (!signal) {
|
|
2226
|
-
return promise;
|
|
2227
|
-
}
|
|
2228
|
-
throwIfAborted(signal);
|
|
2229
|
-
return new Promise((resolve, reject) => {
|
|
2230
|
-
const onAbort = () => {
|
|
2231
|
-
cleanup();
|
|
2232
|
-
reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
|
|
2233
|
-
};
|
|
2234
|
-
const cleanup = () => {
|
|
2235
|
-
signal.removeEventListener("abort", onAbort);
|
|
2236
|
-
};
|
|
2237
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
2238
|
-
promise.then(
|
|
2239
|
-
(value) => {
|
|
2240
|
-
cleanup();
|
|
2241
|
-
resolve(value);
|
|
2242
|
-
},
|
|
2243
|
-
(error) => {
|
|
2244
|
-
cleanup();
|
|
2245
|
-
reject(error);
|
|
2246
|
-
}
|
|
2247
|
-
);
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
async function consumeProcessLogSse(body, listener, signal) {
|
|
2251
|
-
const reader = body.getReader();
|
|
2252
|
-
const decoder = new TextDecoder();
|
|
2253
|
-
let buffer = "";
|
|
2254
|
-
try {
|
|
2255
|
-
while (!signal.aborted) {
|
|
2256
|
-
const { done, value } = await reader.read();
|
|
2257
|
-
if (done) {
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
2261
|
-
let separatorIndex = buffer.indexOf("\n\n");
|
|
2262
|
-
while (separatorIndex !== -1) {
|
|
2263
|
-
const chunk = buffer.slice(0, separatorIndex);
|
|
2264
|
-
buffer = buffer.slice(separatorIndex + 2);
|
|
2265
|
-
const entry = parseProcessLogSseChunk(chunk);
|
|
2266
|
-
if (entry) {
|
|
2267
|
-
listener(entry);
|
|
2268
|
-
}
|
|
2269
|
-
separatorIndex = buffer.indexOf("\n\n");
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
} catch (error) {
|
|
2273
|
-
if (signal.aborted || isAbortError(error)) {
|
|
2274
|
-
return;
|
|
2275
|
-
}
|
|
2276
|
-
throw error;
|
|
2277
|
-
} finally {
|
|
2278
|
-
reader.releaseLock();
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
function parseProcessLogSseChunk(chunk) {
|
|
2282
|
-
if (!chunk.trim()) {
|
|
2283
|
-
return null;
|
|
2284
|
-
}
|
|
2285
|
-
let eventName = "message";
|
|
2286
|
-
const dataLines = [];
|
|
2287
|
-
for (const line of chunk.split("\n")) {
|
|
2288
|
-
if (!line || line.startsWith(":")) {
|
|
2289
|
-
continue;
|
|
2290
|
-
}
|
|
2291
|
-
if (line.startsWith("event:")) {
|
|
2292
|
-
eventName = line.slice(6).trim();
|
|
2293
|
-
continue;
|
|
2294
|
-
}
|
|
2295
|
-
if (line.startsWith("data:")) {
|
|
2296
|
-
dataLines.push(line.slice(5).trimStart());
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
if (eventName !== "log") {
|
|
2300
|
-
return null;
|
|
2301
|
-
}
|
|
2302
|
-
const data = dataLines.join("\n");
|
|
2303
|
-
if (!data.trim()) {
|
|
2304
|
-
return null;
|
|
2305
|
-
}
|
|
2306
|
-
return JSON.parse(data);
|
|
2307
|
-
}
|
|
2308
|
-
function toWebSocketUrl(url) {
|
|
2309
|
-
const parsed = new URL(url);
|
|
2310
|
-
if (parsed.protocol === "http:") {
|
|
2311
|
-
parsed.protocol = "ws:";
|
|
2312
|
-
} else if (parsed.protocol === "https:") {
|
|
2313
|
-
parsed.protocol = "wss:";
|
|
2314
|
-
}
|
|
2315
|
-
return parsed.toString();
|
|
2316
|
-
}
|
|
2317
|
-
function isAbortError(error) {
|
|
2318
|
-
return error instanceof Error && error.name === "AbortError";
|
|
2319
|
-
}
|
|
2320
|
-
function createAbortError(reason) {
|
|
2321
|
-
if (reason instanceof Error) {
|
|
2322
|
-
return reason;
|
|
2323
|
-
}
|
|
2324
|
-
const message = typeof reason === "string" ? reason : "This operation was aborted.";
|
|
2325
|
-
if (typeof DOMException !== "undefined") {
|
|
2326
|
-
return new DOMException(message, "AbortError");
|
|
2327
|
-
}
|
|
2328
|
-
const error = new Error(message);
|
|
2329
|
-
error.name = "AbortError";
|
|
2330
|
-
return error;
|
|
2331
|
-
}
|
|
2332
|
-
function sleep(ms, signal) {
|
|
2333
|
-
if (!signal) {
|
|
2334
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2335
|
-
}
|
|
2336
|
-
throwIfAborted(signal);
|
|
2337
|
-
return new Promise((resolve, reject) => {
|
|
2338
|
-
const timer = setTimeout(() => {
|
|
2339
|
-
cleanup();
|
|
2340
|
-
resolve();
|
|
2341
|
-
}, ms);
|
|
2342
|
-
const onAbort = () => {
|
|
2343
|
-
cleanup();
|
|
2344
|
-
reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
|
|
2345
|
-
};
|
|
2346
|
-
const cleanup = () => {
|
|
2347
|
-
clearTimeout(timer);
|
|
2348
|
-
signal.removeEventListener("abort", onAbort);
|
|
2349
|
-
};
|
|
2350
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2
|
+
DesktopStreamSession,
|
|
3
|
+
InMemorySessionPersistDriver,
|
|
4
|
+
LiveAcpConnection,
|
|
5
|
+
ProcessTerminalSession,
|
|
6
|
+
SandboxAgent,
|
|
7
|
+
SandboxAgentError,
|
|
8
|
+
SandboxDestroyedError,
|
|
9
|
+
Session,
|
|
10
|
+
UnsupportedPermissionReplyError,
|
|
11
|
+
UnsupportedSessionCategoryError,
|
|
12
|
+
UnsupportedSessionConfigOptionError,
|
|
13
|
+
UnsupportedSessionValueError
|
|
14
|
+
} from "./chunk-TVCDKGSM.js";
|
|
2353
15
|
|
|
2354
16
|
// src/index.ts
|
|
2355
|
-
import { AcpRpcError
|
|
17
|
+
import { AcpRpcError } from "acp-http-client";
|
|
2356
18
|
|
|
2357
19
|
// src/inspector.ts
|
|
2358
20
|
function buildInspectorUrl(options) {
|
|
@@ -2368,12 +30,14 @@ function buildInspectorUrl(options) {
|
|
|
2368
30
|
return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
|
|
2369
31
|
}
|
|
2370
32
|
export {
|
|
2371
|
-
|
|
33
|
+
AcpRpcError,
|
|
34
|
+
DesktopStreamSession,
|
|
2372
35
|
InMemorySessionPersistDriver,
|
|
2373
36
|
LiveAcpConnection,
|
|
2374
37
|
ProcessTerminalSession,
|
|
2375
38
|
SandboxAgent,
|
|
2376
39
|
SandboxAgentError,
|
|
40
|
+
SandboxDestroyedError,
|
|
2377
41
|
Session,
|
|
2378
42
|
UnsupportedPermissionReplyError,
|
|
2379
43
|
UnsupportedSessionCategoryError,
|