sandbox-agent 0.2.0 → 0.2.2
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/index.d.ts +759 -13
- package/dist/index.js +903 -34
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
2
|
import {
|
|
3
3
|
AcpHttpClient,
|
|
4
|
+
AcpRpcError,
|
|
4
5
|
PROTOCOL_VERSION
|
|
5
6
|
} from "acp-http-client";
|
|
6
7
|
|
|
@@ -82,7 +83,9 @@ var InMemorySessionPersistDriver = class {
|
|
|
82
83
|
function cloneSessionRecord(session) {
|
|
83
84
|
return {
|
|
84
85
|
...session,
|
|
85
|
-
sessionInit: session.sessionInit ? JSON.parse(JSON.stringify(session.sessionInit)) : void 0
|
|
86
|
+
sessionInit: session.sessionInit ? JSON.parse(JSON.stringify(session.sessionInit)) : void 0,
|
|
87
|
+
configOptions: session.configOptions ? JSON.parse(JSON.stringify(session.configOptions)) : void 0,
|
|
88
|
+
modes: session.modes ? JSON.parse(JSON.stringify(session.modes)) : session.modes
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
91
|
function cloneSessionEvent(event) {
|
|
@@ -121,9 +124,16 @@ function parseCursor(cursor) {
|
|
|
121
124
|
// src/client.ts
|
|
122
125
|
var API_PREFIX = "/v1";
|
|
123
126
|
var FS_PATH = `${API_PREFIX}/fs`;
|
|
127
|
+
var DEFAULT_BASE_URL = "http://sandbox-agent";
|
|
124
128
|
var DEFAULT_REPLAY_MAX_EVENTS = 50;
|
|
125
129
|
var DEFAULT_REPLAY_MAX_CHARS = 12e3;
|
|
126
130
|
var EVENT_INDEX_SCAN_EVENTS_LIMIT = 500;
|
|
131
|
+
var SESSION_CANCEL_METHOD = "session/cancel";
|
|
132
|
+
var MANUAL_CANCEL_ERROR = "Manual session/cancel calls are not allowed. Use destroySession(sessionId) instead.";
|
|
133
|
+
var HEALTH_WAIT_MIN_DELAY_MS = 500;
|
|
134
|
+
var HEALTH_WAIT_MAX_DELAY_MS = 15e3;
|
|
135
|
+
var HEALTH_WAIT_LOG_AFTER_MS = 5e3;
|
|
136
|
+
var HEALTH_WAIT_LOG_EVERY_MS = 1e4;
|
|
127
137
|
var SandboxAgentError = class extends Error {
|
|
128
138
|
status;
|
|
129
139
|
problem;
|
|
@@ -136,6 +146,52 @@ var SandboxAgentError = class extends Error {
|
|
|
136
146
|
this.response = response;
|
|
137
147
|
}
|
|
138
148
|
};
|
|
149
|
+
var UnsupportedSessionCategoryError = class extends Error {
|
|
150
|
+
sessionId;
|
|
151
|
+
category;
|
|
152
|
+
availableCategories;
|
|
153
|
+
constructor(sessionId, category, availableCategories) {
|
|
154
|
+
super(
|
|
155
|
+
`Session '${sessionId}' does not support category '${category}'. Available categories: ${availableCategories.join(", ") || "(none)"}`
|
|
156
|
+
);
|
|
157
|
+
this.name = "UnsupportedSessionCategoryError";
|
|
158
|
+
this.sessionId = sessionId;
|
|
159
|
+
this.category = category;
|
|
160
|
+
this.availableCategories = availableCategories;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var UnsupportedSessionValueError = class extends Error {
|
|
164
|
+
sessionId;
|
|
165
|
+
category;
|
|
166
|
+
configId;
|
|
167
|
+
requestedValue;
|
|
168
|
+
allowedValues;
|
|
169
|
+
constructor(sessionId, category, configId, requestedValue, allowedValues) {
|
|
170
|
+
super(
|
|
171
|
+
`Session '${sessionId}' does not support value '${requestedValue}' for category '${category}' (configId='${configId}'). Allowed values: ${allowedValues.join(", ") || "(none)"}`
|
|
172
|
+
);
|
|
173
|
+
this.name = "UnsupportedSessionValueError";
|
|
174
|
+
this.sessionId = sessionId;
|
|
175
|
+
this.category = category;
|
|
176
|
+
this.configId = configId;
|
|
177
|
+
this.requestedValue = requestedValue;
|
|
178
|
+
this.allowedValues = allowedValues;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
var UnsupportedSessionConfigOptionError = class extends Error {
|
|
182
|
+
sessionId;
|
|
183
|
+
configId;
|
|
184
|
+
availableConfigIds;
|
|
185
|
+
constructor(sessionId, configId, availableConfigIds) {
|
|
186
|
+
super(
|
|
187
|
+
`Session '${sessionId}' does not expose config option '${configId}'. Available configIds: ${availableConfigIds.join(", ") || "(none)"}`
|
|
188
|
+
);
|
|
189
|
+
this.name = "UnsupportedSessionConfigOptionError";
|
|
190
|
+
this.sessionId = sessionId;
|
|
191
|
+
this.configId = configId;
|
|
192
|
+
this.availableConfigIds = availableConfigIds;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
139
195
|
var Session = class {
|
|
140
196
|
record;
|
|
141
197
|
sandbox;
|
|
@@ -178,6 +234,32 @@ var Session = class {
|
|
|
178
234
|
const response = await this.send("session/prompt", { prompt });
|
|
179
235
|
return response;
|
|
180
236
|
}
|
|
237
|
+
async setMode(modeId) {
|
|
238
|
+
const updated = await this.sandbox.setSessionMode(this.id, modeId);
|
|
239
|
+
this.apply(updated.session.toRecord());
|
|
240
|
+
return updated.response;
|
|
241
|
+
}
|
|
242
|
+
async setConfigOption(configId, value) {
|
|
243
|
+
const updated = await this.sandbox.setSessionConfigOption(this.id, configId, value);
|
|
244
|
+
this.apply(updated.session.toRecord());
|
|
245
|
+
return updated.response;
|
|
246
|
+
}
|
|
247
|
+
async setModel(model) {
|
|
248
|
+
const updated = await this.sandbox.setSessionModel(this.id, model);
|
|
249
|
+
this.apply(updated.session.toRecord());
|
|
250
|
+
return updated.response;
|
|
251
|
+
}
|
|
252
|
+
async setThoughtLevel(thoughtLevel) {
|
|
253
|
+
const updated = await this.sandbox.setSessionThoughtLevel(this.id, thoughtLevel);
|
|
254
|
+
this.apply(updated.session.toRecord());
|
|
255
|
+
return updated.response;
|
|
256
|
+
}
|
|
257
|
+
async getConfigOptions() {
|
|
258
|
+
return this.sandbox.getSessionConfigOptions(this.id);
|
|
259
|
+
}
|
|
260
|
+
async getModes() {
|
|
261
|
+
return this.sandbox.getSessionModes(this.id);
|
|
262
|
+
}
|
|
181
263
|
onEvent(listener) {
|
|
182
264
|
return this.sandbox.onSessionEvent(this.id, listener);
|
|
183
265
|
}
|
|
@@ -197,6 +279,8 @@ var LiveAcpConnection = class _LiveAcpConnection {
|
|
|
197
279
|
pendingNewSessionLocals = [];
|
|
198
280
|
pendingRequestSessionById = /* @__PURE__ */ new Map();
|
|
199
281
|
pendingReplayByLocalSessionId = /* @__PURE__ */ new Map();
|
|
282
|
+
lastAdapterExit = null;
|
|
283
|
+
lastAdapterExitAt = 0;
|
|
200
284
|
onObservedEnvelope;
|
|
201
285
|
constructor(agent, connectionId, acp, onObservedEnvelope) {
|
|
202
286
|
this.agent = agent;
|
|
@@ -218,6 +302,10 @@ var LiveAcpConnection = class _LiveAcpConnection {
|
|
|
218
302
|
},
|
|
219
303
|
client: {
|
|
220
304
|
sessionUpdate: async (_notification) => {
|
|
305
|
+
},
|
|
306
|
+
extNotification: async (method, params) => {
|
|
307
|
+
if (!live) return;
|
|
308
|
+
live.handleAdapterNotification(method, params);
|
|
221
309
|
}
|
|
222
310
|
},
|
|
223
311
|
onEnvelope: (envelope, direction) => {
|
|
@@ -265,6 +353,7 @@ var LiveAcpConnection = class _LiveAcpConnection {
|
|
|
265
353
|
this.pendingReplayByLocalSessionId.set(localSessionId, replayText);
|
|
266
354
|
}
|
|
267
355
|
async createRemoteSession(localSessionId, sessionInit) {
|
|
356
|
+
const createStartedAt = Date.now();
|
|
268
357
|
this.pendingNewSessionLocals.push(localSessionId);
|
|
269
358
|
try {
|
|
270
359
|
const response = await this.acp.newSession(sessionInit);
|
|
@@ -275,6 +364,11 @@ var LiveAcpConnection = class _LiveAcpConnection {
|
|
|
275
364
|
if (index !== -1) {
|
|
276
365
|
this.pendingNewSessionLocals.splice(index, 1);
|
|
277
366
|
}
|
|
367
|
+
const adapterExit = this.lastAdapterExit;
|
|
368
|
+
if (adapterExit && this.lastAdapterExitAt >= createStartedAt) {
|
|
369
|
+
const suffix = adapterExit.code == null ? "" : ` (code ${adapterExit.code})`;
|
|
370
|
+
throw new Error(`Agent process exited while creating session${suffix}`);
|
|
371
|
+
}
|
|
278
372
|
throw error;
|
|
279
373
|
}
|
|
280
374
|
}
|
|
@@ -316,6 +410,16 @@ var LiveAcpConnection = class _LiveAcpConnection {
|
|
|
316
410
|
const localSessionId = this.resolveSessionId(envelope, direction);
|
|
317
411
|
this.onObservedEnvelope(this, envelope, direction, localSessionId);
|
|
318
412
|
}
|
|
413
|
+
handleAdapterNotification(method, params) {
|
|
414
|
+
if (method !== "_adapter/agent_exited") {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.lastAdapterExit = {
|
|
418
|
+
success: params.success === true,
|
|
419
|
+
code: typeof params.code === "number" ? params.code : null
|
|
420
|
+
};
|
|
421
|
+
this.lastAdapterExitAt = Date.now();
|
|
422
|
+
}
|
|
319
423
|
resolveSessionId(envelope, direction) {
|
|
320
424
|
const id = envelopeId(envelope);
|
|
321
425
|
const method = envelopeMethod(envelope);
|
|
@@ -359,26 +463,39 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
359
463
|
token;
|
|
360
464
|
fetcher;
|
|
361
465
|
defaultHeaders;
|
|
466
|
+
healthWait;
|
|
467
|
+
healthWaitAbortController = new AbortController();
|
|
362
468
|
persist;
|
|
363
469
|
replayMaxEvents;
|
|
364
470
|
replayMaxChars;
|
|
365
471
|
spawnHandle;
|
|
472
|
+
healthPromise;
|
|
473
|
+
healthError;
|
|
474
|
+
disposed = false;
|
|
366
475
|
liveConnections = /* @__PURE__ */ new Map();
|
|
476
|
+
pendingLiveConnections = /* @__PURE__ */ new Map();
|
|
367
477
|
sessionHandles = /* @__PURE__ */ new Map();
|
|
368
478
|
eventListeners = /* @__PURE__ */ new Map();
|
|
369
479
|
nextSessionEventIndexBySession = /* @__PURE__ */ new Map();
|
|
370
480
|
seedSessionEventIndexBySession = /* @__PURE__ */ new Map();
|
|
371
481
|
constructor(options) {
|
|
372
|
-
|
|
482
|
+
const baseUrl = options.baseUrl?.trim();
|
|
483
|
+
if (!baseUrl && !options.fetch) {
|
|
484
|
+
throw new Error("baseUrl is required unless fetch is provided.");
|
|
485
|
+
}
|
|
486
|
+
this.baseUrl = (baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
373
487
|
this.token = options.token;
|
|
374
|
-
|
|
488
|
+
const resolvedFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
489
|
+
if (!resolvedFetch) {
|
|
490
|
+
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
|
491
|
+
}
|
|
492
|
+
this.fetcher = resolvedFetch;
|
|
375
493
|
this.defaultHeaders = options.headers;
|
|
494
|
+
this.healthWait = normalizeHealthWaitOptions(options.waitForHealth, options.signal);
|
|
376
495
|
this.persist = options.persist ?? new InMemorySessionPersistDriver();
|
|
377
496
|
this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
|
|
378
497
|
this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
|
|
379
|
-
|
|
380
|
-
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
|
381
|
-
}
|
|
498
|
+
this.startHealthWait();
|
|
382
499
|
}
|
|
383
500
|
static async connect(options) {
|
|
384
501
|
return new _SandboxAgent(options);
|
|
@@ -389,12 +506,14 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
389
506
|
throw new Error("SandboxAgent.start requires spawn to be enabled.");
|
|
390
507
|
}
|
|
391
508
|
const { spawnSandboxAgent } = await import("./spawn-BQVVCZX7.js");
|
|
392
|
-
const
|
|
509
|
+
const resolvedFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
510
|
+
const handle = await spawnSandboxAgent(spawnOptions, resolvedFetch);
|
|
393
511
|
const client = new _SandboxAgent({
|
|
394
512
|
baseUrl: handle.baseUrl,
|
|
395
513
|
token: handle.token,
|
|
396
514
|
fetch: options.fetch,
|
|
397
515
|
headers: options.headers,
|
|
516
|
+
waitForHealth: false,
|
|
398
517
|
persist: options.persist,
|
|
399
518
|
replayMaxEvents: options.replayMaxEvents,
|
|
400
519
|
replayMaxChars: options.replayMaxChars
|
|
@@ -403,8 +522,18 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
403
522
|
return client;
|
|
404
523
|
}
|
|
405
524
|
async dispose() {
|
|
525
|
+
this.disposed = true;
|
|
526
|
+
this.healthWaitAbortController.abort(createAbortError("SandboxAgent was disposed."));
|
|
406
527
|
const connections = [...this.liveConnections.values()];
|
|
407
528
|
this.liveConnections.clear();
|
|
529
|
+
const pending = [...this.pendingLiveConnections.values()];
|
|
530
|
+
this.pendingLiveConnections.clear();
|
|
531
|
+
const pendingSettled = await Promise.allSettled(pending);
|
|
532
|
+
for (const item of pendingSettled) {
|
|
533
|
+
if (item.status === "fulfilled") {
|
|
534
|
+
connections.push(item.value);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
408
537
|
await Promise.all(
|
|
409
538
|
connections.map(async (connection) => {
|
|
410
539
|
await connection.close();
|
|
@@ -446,12 +575,32 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
446
575
|
agentSessionId: response.sessionId,
|
|
447
576
|
lastConnectionId: live.connectionId,
|
|
448
577
|
createdAt: nowMs(),
|
|
449
|
-
sessionInit
|
|
578
|
+
sessionInit,
|
|
579
|
+
configOptions: cloneConfigOptions(response.configOptions),
|
|
580
|
+
modes: cloneModes(response.modes)
|
|
450
581
|
};
|
|
451
582
|
await this.persist.updateSession(record);
|
|
452
583
|
this.nextSessionEventIndexBySession.set(record.id, 1);
|
|
453
584
|
live.bindSession(record.id, record.agentSessionId);
|
|
454
|
-
|
|
585
|
+
let session = this.upsertSessionHandle(record);
|
|
586
|
+
try {
|
|
587
|
+
if (request.mode) {
|
|
588
|
+
session = (await this.setSessionMode(session.id, request.mode)).session;
|
|
589
|
+
}
|
|
590
|
+
if (request.model) {
|
|
591
|
+
session = (await this.setSessionModel(session.id, request.model)).session;
|
|
592
|
+
}
|
|
593
|
+
if (request.thoughtLevel) {
|
|
594
|
+
session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
|
|
595
|
+
}
|
|
596
|
+
} catch (err) {
|
|
597
|
+
try {
|
|
598
|
+
await this.destroySession(session.id);
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
throw err;
|
|
602
|
+
}
|
|
603
|
+
return session;
|
|
455
604
|
}
|
|
456
605
|
async resumeSession(id) {
|
|
457
606
|
const existing = await this.persist.getSession(id);
|
|
@@ -469,7 +618,9 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
469
618
|
...existing,
|
|
470
619
|
agentSessionId: recreated.sessionId,
|
|
471
620
|
lastConnectionId: live.connectionId,
|
|
472
|
-
destroyedAt: void 0
|
|
621
|
+
destroyedAt: void 0,
|
|
622
|
+
configOptions: cloneConfigOptions(recreated.configOptions),
|
|
623
|
+
modes: cloneModes(recreated.modes)
|
|
473
624
|
};
|
|
474
625
|
await this.persist.updateSession(updated);
|
|
475
626
|
live.bindSession(updated.id, updated.agentSessionId);
|
|
@@ -479,15 +630,26 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
479
630
|
async resumeOrCreateSession(request) {
|
|
480
631
|
const existing = await this.persist.getSession(request.id);
|
|
481
632
|
if (existing) {
|
|
482
|
-
|
|
633
|
+
let session = await this.resumeSession(existing.id);
|
|
634
|
+
if (request.mode) {
|
|
635
|
+
session = (await this.setSessionMode(session.id, request.mode)).session;
|
|
636
|
+
}
|
|
637
|
+
if (request.model) {
|
|
638
|
+
session = (await this.setSessionModel(session.id, request.model)).session;
|
|
639
|
+
}
|
|
640
|
+
if (request.thoughtLevel) {
|
|
641
|
+
session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
|
|
642
|
+
}
|
|
643
|
+
return session;
|
|
483
644
|
}
|
|
484
645
|
return this.createSession(request);
|
|
485
646
|
}
|
|
486
647
|
async destroySession(id) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
648
|
+
try {
|
|
649
|
+
await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true);
|
|
650
|
+
} catch {
|
|
490
651
|
}
|
|
652
|
+
const existing = await this.requireSessionRecord(id);
|
|
491
653
|
const updated = {
|
|
492
654
|
...existing,
|
|
493
655
|
destroyedAt: nowMs()
|
|
@@ -495,7 +657,132 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
495
657
|
await this.persist.updateSession(updated);
|
|
496
658
|
return this.upsertSessionHandle(updated);
|
|
497
659
|
}
|
|
660
|
+
async setSessionMode(sessionId, modeId) {
|
|
661
|
+
const mode = modeId.trim();
|
|
662
|
+
if (!mode) {
|
|
663
|
+
throw new Error("setSessionMode requires a non-empty modeId");
|
|
664
|
+
}
|
|
665
|
+
const record = await this.requireSessionRecord(sessionId);
|
|
666
|
+
const knownModeIds = extractKnownModeIds(record.modes);
|
|
667
|
+
if (knownModeIds.length > 0 && !knownModeIds.includes(mode)) {
|
|
668
|
+
throw new UnsupportedSessionValueError(sessionId, "mode", "mode", mode, knownModeIds);
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
return await this.sendSessionMethodInternal(
|
|
672
|
+
sessionId,
|
|
673
|
+
"session/set_mode",
|
|
674
|
+
{ modeId: mode },
|
|
675
|
+
{},
|
|
676
|
+
false
|
|
677
|
+
);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (!(error instanceof AcpRpcError) || error.code !== -32601) {
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
return this.setSessionCategoryValue(sessionId, "mode", mode);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async setSessionConfigOption(sessionId, configId, value) {
|
|
686
|
+
const resolvedConfigId = configId.trim();
|
|
687
|
+
if (!resolvedConfigId) {
|
|
688
|
+
throw new Error("setSessionConfigOption requires a non-empty configId");
|
|
689
|
+
}
|
|
690
|
+
const resolvedValue = value.trim();
|
|
691
|
+
if (!resolvedValue) {
|
|
692
|
+
throw new Error("setSessionConfigOption requires a non-empty value");
|
|
693
|
+
}
|
|
694
|
+
const options = await this.getSessionConfigOptions(sessionId);
|
|
695
|
+
const option = findConfigOptionById(options, resolvedConfigId);
|
|
696
|
+
if (!option) {
|
|
697
|
+
throw new UnsupportedSessionConfigOptionError(
|
|
698
|
+
sessionId,
|
|
699
|
+
resolvedConfigId,
|
|
700
|
+
options.map((item) => item.id)
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
const allowedValues = extractConfigValues(option);
|
|
704
|
+
if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
|
|
705
|
+
throw new UnsupportedSessionValueError(
|
|
706
|
+
sessionId,
|
|
707
|
+
option.category ?? "uncategorized",
|
|
708
|
+
option.id,
|
|
709
|
+
resolvedValue,
|
|
710
|
+
allowedValues
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
return await this.sendSessionMethodInternal(
|
|
714
|
+
sessionId,
|
|
715
|
+
"session/set_config_option",
|
|
716
|
+
{
|
|
717
|
+
configId: resolvedConfigId,
|
|
718
|
+
value: resolvedValue
|
|
719
|
+
},
|
|
720
|
+
{},
|
|
721
|
+
false
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
async setSessionModel(sessionId, model) {
|
|
725
|
+
return this.setSessionCategoryValue(sessionId, "model", model);
|
|
726
|
+
}
|
|
727
|
+
async setSessionThoughtLevel(sessionId, thoughtLevel) {
|
|
728
|
+
return this.setSessionCategoryValue(sessionId, "thought_level", thoughtLevel);
|
|
729
|
+
}
|
|
730
|
+
async getSessionConfigOptions(sessionId) {
|
|
731
|
+
const record = await this.requireSessionRecord(sessionId);
|
|
732
|
+
const hydrated = await this.hydrateSessionConfigOptions(record.id, record);
|
|
733
|
+
return cloneConfigOptions(hydrated.configOptions) ?? [];
|
|
734
|
+
}
|
|
735
|
+
async getSessionModes(sessionId) {
|
|
736
|
+
const record = await this.requireSessionRecord(sessionId);
|
|
737
|
+
return cloneModes(record.modes);
|
|
738
|
+
}
|
|
739
|
+
async setSessionCategoryValue(sessionId, category, value) {
|
|
740
|
+
const resolvedValue = value.trim();
|
|
741
|
+
if (!resolvedValue) {
|
|
742
|
+
throw new Error(`setSession${toTitleCase(category)} requires a non-empty value`);
|
|
743
|
+
}
|
|
744
|
+
const options = await this.getSessionConfigOptions(sessionId);
|
|
745
|
+
const option = findConfigOptionByCategory(options, category);
|
|
746
|
+
if (!option) {
|
|
747
|
+
const categories = uniqueCategories(options);
|
|
748
|
+
throw new UnsupportedSessionCategoryError(sessionId, category, categories);
|
|
749
|
+
}
|
|
750
|
+
const allowedValues = extractConfigValues(option);
|
|
751
|
+
if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
|
|
752
|
+
throw new UnsupportedSessionValueError(
|
|
753
|
+
sessionId,
|
|
754
|
+
category,
|
|
755
|
+
option.id,
|
|
756
|
+
resolvedValue,
|
|
757
|
+
allowedValues
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return this.setSessionConfigOption(sessionId, option.id, resolvedValue);
|
|
761
|
+
}
|
|
762
|
+
async hydrateSessionConfigOptions(sessionId, snapshot) {
|
|
763
|
+
if (snapshot.configOptions !== void 0) {
|
|
764
|
+
return snapshot;
|
|
765
|
+
}
|
|
766
|
+
const info = await this.getAgent(snapshot.agent, { config: true });
|
|
767
|
+
const configOptions = normalizeSessionConfigOptions(info.configOptions) ?? [];
|
|
768
|
+
const record = await this.persist.getSession(sessionId);
|
|
769
|
+
if (!record) {
|
|
770
|
+
return { ...snapshot, configOptions };
|
|
771
|
+
}
|
|
772
|
+
const updated = {
|
|
773
|
+
...record,
|
|
774
|
+
configOptions
|
|
775
|
+
};
|
|
776
|
+
await this.persist.updateSession(updated);
|
|
777
|
+
return updated;
|
|
778
|
+
}
|
|
498
779
|
async sendSessionMethod(sessionId, method, params, options = {}) {
|
|
780
|
+
return this.sendSessionMethodInternal(sessionId, method, params, options, false);
|
|
781
|
+
}
|
|
782
|
+
async sendSessionMethodInternal(sessionId, method, params, options, allowManagedCancel) {
|
|
783
|
+
if (method === SESSION_CANCEL_METHOD && !allowManagedCancel) {
|
|
784
|
+
throw new Error(MANUAL_CANCEL_ERROR);
|
|
785
|
+
}
|
|
499
786
|
const record = await this.persist.getSession(sessionId);
|
|
500
787
|
if (!record) {
|
|
501
788
|
throw new Error(`session '${sessionId}' not found`);
|
|
@@ -503,15 +790,73 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
503
790
|
const live = await this.getLiveConnection(record.agent);
|
|
504
791
|
if (!live.hasBoundSession(record.id, record.agentSessionId)) {
|
|
505
792
|
const restored = await this.resumeSession(record.id);
|
|
506
|
-
return this.
|
|
793
|
+
return this.sendSessionMethodInternal(restored.id, method, params, options, allowManagedCancel);
|
|
507
794
|
}
|
|
508
795
|
const response = await live.sendSessionMethod(record.id, method, params, options);
|
|
796
|
+
await this.persistSessionStateFromMethod(record.id, method, params, response);
|
|
509
797
|
const refreshed = await this.requireSessionRecord(record.id);
|
|
510
798
|
return {
|
|
511
799
|
session: this.upsertSessionHandle(refreshed),
|
|
512
800
|
response
|
|
513
801
|
};
|
|
514
802
|
}
|
|
803
|
+
async persistSessionStateFromMethod(sessionId, method, params, response) {
|
|
804
|
+
const record = await this.persist.getSession(sessionId);
|
|
805
|
+
if (!record) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (method === "session/set_config_option") {
|
|
809
|
+
const configId = typeof params.configId === "string" ? params.configId : null;
|
|
810
|
+
const value = typeof params.value === "string" ? params.value : null;
|
|
811
|
+
const updates = {};
|
|
812
|
+
const serverConfigOptions = extractConfigOptionsFromSetResponse(response);
|
|
813
|
+
if (serverConfigOptions) {
|
|
814
|
+
updates.configOptions = cloneConfigOptions(serverConfigOptions);
|
|
815
|
+
} else if (record.configOptions && configId && value) {
|
|
816
|
+
const updated = applyConfigOptionValue(record.configOptions, configId, value);
|
|
817
|
+
if (updated) {
|
|
818
|
+
updates.configOptions = updated;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (configId && value) {
|
|
822
|
+
const source = updates.configOptions ?? record.configOptions;
|
|
823
|
+
const option = source ? findConfigOptionById(source, configId) : null;
|
|
824
|
+
if (option?.category === "mode") {
|
|
825
|
+
const nextModes = applyCurrentMode(record.modes, value);
|
|
826
|
+
if (nextModes) {
|
|
827
|
+
updates.modes = nextModes;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (Object.keys(updates).length > 0) {
|
|
832
|
+
await this.persist.updateSession({ ...record, ...updates });
|
|
833
|
+
}
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (method === "session/set_mode") {
|
|
837
|
+
const modeId = typeof params.modeId === "string" ? params.modeId : null;
|
|
838
|
+
if (!modeId) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const updates = {};
|
|
842
|
+
const nextModes = applyCurrentMode(record.modes, modeId);
|
|
843
|
+
if (nextModes) {
|
|
844
|
+
updates.modes = nextModes;
|
|
845
|
+
}
|
|
846
|
+
if (record.configOptions) {
|
|
847
|
+
const modeOption = findConfigOptionByCategory(record.configOptions, "mode");
|
|
848
|
+
if (modeOption) {
|
|
849
|
+
const updated = applyConfigOptionValue(record.configOptions, modeOption.id, modeId);
|
|
850
|
+
if (updated) {
|
|
851
|
+
updates.configOptions = updated;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (Object.keys(updates).length > 0) {
|
|
856
|
+
await this.persist.updateSession({ ...record, ...updates });
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
515
860
|
onSessionEvent(sessionId, listener) {
|
|
516
861
|
const listeners = this.eventListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
517
862
|
listeners.add(listener);
|
|
@@ -528,16 +873,16 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
528
873
|
};
|
|
529
874
|
}
|
|
530
875
|
async getHealth() {
|
|
531
|
-
return this.
|
|
876
|
+
return this.requestHealth();
|
|
532
877
|
}
|
|
533
878
|
async listAgents(options) {
|
|
534
879
|
return this.requestJson("GET", `${API_PREFIX}/agents`, {
|
|
535
|
-
query: options
|
|
880
|
+
query: toAgentQuery(options)
|
|
536
881
|
});
|
|
537
882
|
}
|
|
538
883
|
async getAgent(agent, options) {
|
|
539
884
|
return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
|
|
540
|
-
query: options
|
|
885
|
+
query: toAgentQuery(options)
|
|
541
886
|
});
|
|
542
887
|
}
|
|
543
888
|
async installAgent(agent, request = {}) {
|
|
@@ -609,25 +954,141 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
609
954
|
async deleteSkillsConfig(query) {
|
|
610
955
|
await this.requestRaw("DELETE", `${API_PREFIX}/config/skills`, { query });
|
|
611
956
|
}
|
|
957
|
+
async getProcessConfig() {
|
|
958
|
+
return this.requestJson("GET", `${API_PREFIX}/processes/config`);
|
|
959
|
+
}
|
|
960
|
+
async setProcessConfig(config) {
|
|
961
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/config`, {
|
|
962
|
+
body: config
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
async createProcess(request) {
|
|
966
|
+
return this.requestJson("POST", `${API_PREFIX}/processes`, {
|
|
967
|
+
body: request
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
async runProcess(request) {
|
|
971
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/run`, {
|
|
972
|
+
body: request
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
async listProcesses() {
|
|
976
|
+
return this.requestJson("GET", `${API_PREFIX}/processes`);
|
|
977
|
+
}
|
|
978
|
+
async getProcess(id) {
|
|
979
|
+
return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
|
|
980
|
+
}
|
|
981
|
+
async stopProcess(id, query) {
|
|
982
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/stop`, {
|
|
983
|
+
query
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
async killProcess(id, query) {
|
|
987
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/kill`, {
|
|
988
|
+
query
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
async deleteProcess(id) {
|
|
992
|
+
await this.requestRaw("DELETE", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
|
|
993
|
+
}
|
|
994
|
+
async getProcessLogs(id, query = {}) {
|
|
995
|
+
return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`, {
|
|
996
|
+
query
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
async followProcessLogs(id, listener, query = {}) {
|
|
1000
|
+
const abortController = new AbortController();
|
|
1001
|
+
const response = await this.requestRaw(
|
|
1002
|
+
"GET",
|
|
1003
|
+
`${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`,
|
|
1004
|
+
{
|
|
1005
|
+
query: { ...query, follow: true },
|
|
1006
|
+
accept: "text/event-stream",
|
|
1007
|
+
signal: abortController.signal
|
|
1008
|
+
}
|
|
1009
|
+
);
|
|
1010
|
+
if (!response.body) {
|
|
1011
|
+
abortController.abort();
|
|
1012
|
+
throw new Error("SSE stream is not readable in this environment.");
|
|
1013
|
+
}
|
|
1014
|
+
const closed = consumeProcessLogSse(response.body, listener, abortController.signal);
|
|
1015
|
+
return {
|
|
1016
|
+
close: () => abortController.abort(),
|
|
1017
|
+
closed
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
async sendProcessInput(id, request) {
|
|
1021
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/input`, {
|
|
1022
|
+
body: request
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
async resizeProcessTerminal(id, request) {
|
|
1026
|
+
return this.requestJson(
|
|
1027
|
+
"POST",
|
|
1028
|
+
`${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/resize`,
|
|
1029
|
+
{
|
|
1030
|
+
body: request
|
|
1031
|
+
}
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
buildProcessTerminalWebSocketUrl(id, options = {}) {
|
|
1035
|
+
return toWebSocketUrl(
|
|
1036
|
+
this.buildUrl(`${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/ws`, {
|
|
1037
|
+
access_token: options.accessToken ?? this.token
|
|
1038
|
+
})
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
connectProcessTerminalWebSocket(id, options = {}) {
|
|
1042
|
+
const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket;
|
|
1043
|
+
if (!WebSocketCtor) {
|
|
1044
|
+
throw new Error("WebSocket API is not available; provide a WebSocket implementation.");
|
|
1045
|
+
}
|
|
1046
|
+
return new WebSocketCtor(
|
|
1047
|
+
this.buildProcessTerminalWebSocketUrl(id, {
|
|
1048
|
+
accessToken: options.accessToken
|
|
1049
|
+
}),
|
|
1050
|
+
options.protocols
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
612
1053
|
async getLiveConnection(agent) {
|
|
1054
|
+
await this.awaitHealthy();
|
|
613
1055
|
const existing = this.liveConnections.get(agent);
|
|
614
1056
|
if (existing) {
|
|
615
1057
|
return existing;
|
|
616
1058
|
}
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1059
|
+
const pending = this.pendingLiveConnections.get(agent);
|
|
1060
|
+
if (pending) {
|
|
1061
|
+
return pending;
|
|
1062
|
+
}
|
|
1063
|
+
const creating = (async () => {
|
|
1064
|
+
const serverId = `sdk-${agent}-${randomId()}`;
|
|
1065
|
+
const created = await LiveAcpConnection.create({
|
|
1066
|
+
baseUrl: this.baseUrl,
|
|
1067
|
+
token: this.token,
|
|
1068
|
+
fetcher: this.fetcher,
|
|
1069
|
+
headers: this.defaultHeaders,
|
|
1070
|
+
agent,
|
|
1071
|
+
serverId,
|
|
1072
|
+
onObservedEnvelope: (connection, envelope, direction, localSessionId) => {
|
|
1073
|
+
void this.persistObservedEnvelope(connection, envelope, direction, localSessionId);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
const raced = this.liveConnections.get(agent);
|
|
1077
|
+
if (raced) {
|
|
1078
|
+
await created.close();
|
|
1079
|
+
return raced;
|
|
627
1080
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1081
|
+
this.liveConnections.set(agent, created);
|
|
1082
|
+
return created;
|
|
1083
|
+
})();
|
|
1084
|
+
this.pendingLiveConnections.set(agent, creating);
|
|
1085
|
+
try {
|
|
1086
|
+
return await creating;
|
|
1087
|
+
} finally {
|
|
1088
|
+
if (this.pendingLiveConnections.get(agent) === creating) {
|
|
1089
|
+
this.pendingLiveConnections.delete(agent);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
631
1092
|
}
|
|
632
1093
|
async persistObservedEnvelope(connection, envelope, direction, localSessionId) {
|
|
633
1094
|
if (!localSessionId) {
|
|
@@ -643,6 +1104,7 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
643
1104
|
payload: cloneEnvelope(envelope)
|
|
644
1105
|
};
|
|
645
1106
|
await this.persist.insertEvent(event);
|
|
1107
|
+
await this.persistSessionStateFromEvent(localSessionId, envelope, direction);
|
|
646
1108
|
const listeners = this.eventListeners.get(localSessionId);
|
|
647
1109
|
if (!listeners || listeners.size === 0) {
|
|
648
1110
|
return;
|
|
@@ -651,6 +1113,46 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
651
1113
|
listener(event);
|
|
652
1114
|
}
|
|
653
1115
|
}
|
|
1116
|
+
async persistSessionStateFromEvent(sessionId, envelope, direction) {
|
|
1117
|
+
if (direction !== "inbound") {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (envelopeMethod(envelope) !== "session/update") {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const update = envelopeSessionUpdate(envelope);
|
|
1124
|
+
if (!update || typeof update.sessionUpdate !== "string") {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const record = await this.persist.getSession(sessionId);
|
|
1128
|
+
if (!record) {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (update.sessionUpdate === "config_option_update") {
|
|
1132
|
+
const configOptions = normalizeSessionConfigOptions(update.configOptions);
|
|
1133
|
+
if (configOptions) {
|
|
1134
|
+
await this.persist.updateSession({
|
|
1135
|
+
...record,
|
|
1136
|
+
configOptions
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (update.sessionUpdate === "current_mode_update") {
|
|
1142
|
+
const modeId = typeof update.currentModeId === "string" ? update.currentModeId : null;
|
|
1143
|
+
if (!modeId) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const nextModes = applyCurrentMode(record.modes, modeId);
|
|
1147
|
+
if (!nextModes) {
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
await this.persist.updateSession({
|
|
1151
|
+
...record,
|
|
1152
|
+
modes: nextModes
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
654
1156
|
async allocateSessionEventIndex(sessionId) {
|
|
655
1157
|
await this.ensureSessionEventIndexSeeded(sessionId);
|
|
656
1158
|
const nextIndex = this.nextSessionEventIndexBySession.get(sessionId) ?? 1;
|
|
@@ -736,7 +1238,8 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
736
1238
|
body: options.body,
|
|
737
1239
|
headers: options.headers,
|
|
738
1240
|
accept: options.accept ?? "application/json",
|
|
739
|
-
signal: options.signal
|
|
1241
|
+
signal: options.signal,
|
|
1242
|
+
skipReadyWait: options.skipReadyWait
|
|
740
1243
|
});
|
|
741
1244
|
if (response.status === 204) {
|
|
742
1245
|
return void 0;
|
|
@@ -744,6 +1247,9 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
744
1247
|
return await response.json();
|
|
745
1248
|
}
|
|
746
1249
|
async requestRaw(method, path, options = {}) {
|
|
1250
|
+
if (!options.skipReadyWait) {
|
|
1251
|
+
await this.awaitHealthy(options.signal);
|
|
1252
|
+
}
|
|
747
1253
|
const url = this.buildUrl(path, options.query);
|
|
748
1254
|
const headers = this.buildHeaders(options.headers);
|
|
749
1255
|
if (options.accept) {
|
|
@@ -773,6 +1279,64 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
773
1279
|
}
|
|
774
1280
|
return response;
|
|
775
1281
|
}
|
|
1282
|
+
startHealthWait() {
|
|
1283
|
+
if (!this.healthWait.enabled || this.healthPromise) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
this.healthPromise = this.runHealthWait().catch((error) => {
|
|
1287
|
+
this.healthError = error instanceof Error ? error : new Error(String(error));
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
async awaitHealthy(signal) {
|
|
1291
|
+
if (!this.healthPromise) {
|
|
1292
|
+
throwIfAborted(signal);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
await waitForAbortable(this.healthPromise, signal);
|
|
1296
|
+
throwIfAborted(signal);
|
|
1297
|
+
if (this.healthError) {
|
|
1298
|
+
throw this.healthError;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
async runHealthWait() {
|
|
1302
|
+
const signal = this.healthWait.enabled ? anyAbortSignal([this.healthWait.signal, this.healthWaitAbortController.signal]) : void 0;
|
|
1303
|
+
const startedAt = Date.now();
|
|
1304
|
+
const deadline = typeof this.healthWait.timeoutMs === "number" ? startedAt + this.healthWait.timeoutMs : void 0;
|
|
1305
|
+
let delayMs = HEALTH_WAIT_MIN_DELAY_MS;
|
|
1306
|
+
let nextLogAt = startedAt + HEALTH_WAIT_LOG_AFTER_MS;
|
|
1307
|
+
let lastError;
|
|
1308
|
+
while (!this.disposed && (deadline === void 0 || Date.now() < deadline)) {
|
|
1309
|
+
throwIfAborted(signal);
|
|
1310
|
+
try {
|
|
1311
|
+
const health = await this.requestHealth({ signal });
|
|
1312
|
+
if (health.status === "ok") {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
lastError = new Error(`Unexpected health response: ${JSON.stringify(health)}`);
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
if (isAbortError(error)) {
|
|
1318
|
+
throw error;
|
|
1319
|
+
}
|
|
1320
|
+
lastError = error;
|
|
1321
|
+
}
|
|
1322
|
+
const now = Date.now();
|
|
1323
|
+
if (now >= nextLogAt) {
|
|
1324
|
+
const details = formatHealthWaitError(lastError);
|
|
1325
|
+
console.warn(
|
|
1326
|
+
`sandbox-agent at ${this.baseUrl} is not healthy after ${now - startedAt}ms; still waiting (${details})`
|
|
1327
|
+
);
|
|
1328
|
+
nextLogAt = now + HEALTH_WAIT_LOG_EVERY_MS;
|
|
1329
|
+
}
|
|
1330
|
+
await sleep(delayMs, signal);
|
|
1331
|
+
delayMs = Math.min(HEALTH_WAIT_MAX_DELAY_MS, delayMs * 2);
|
|
1332
|
+
}
|
|
1333
|
+
if (this.disposed) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
throw new Error(
|
|
1337
|
+
`Timed out waiting for sandbox-agent health after ${this.healthWait.timeoutMs}ms (${formatHealthWaitError(lastError)})`
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
776
1340
|
buildHeaders(extra) {
|
|
777
1341
|
const headers = new Headers(this.defaultHeaders ?? void 0);
|
|
778
1342
|
if (this.token) {
|
|
@@ -796,6 +1360,12 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
796
1360
|
}
|
|
797
1361
|
return url.toString();
|
|
798
1362
|
}
|
|
1363
|
+
async requestHealth(options = {}) {
|
|
1364
|
+
return this.requestJson("GET", `${API_PREFIX}/health`, {
|
|
1365
|
+
signal: options.signal,
|
|
1366
|
+
skipReadyWait: true
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
799
1369
|
};
|
|
800
1370
|
async function autoAuthenticate(acp, methods) {
|
|
801
1371
|
const envBased = methods.find(
|
|
@@ -809,6 +1379,15 @@ async function autoAuthenticate(acp, methods) {
|
|
|
809
1379
|
} catch {
|
|
810
1380
|
}
|
|
811
1381
|
}
|
|
1382
|
+
function toAgentQuery(options) {
|
|
1383
|
+
if (!options) {
|
|
1384
|
+
return void 0;
|
|
1385
|
+
}
|
|
1386
|
+
return {
|
|
1387
|
+
config: options.config,
|
|
1388
|
+
no_cache: options.noCache
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
812
1391
|
function normalizeSessionInit(value) {
|
|
813
1392
|
if (!value) {
|
|
814
1393
|
return {
|
|
@@ -916,6 +1495,20 @@ function normalizePositiveInt(value, fallback) {
|
|
|
916
1495
|
}
|
|
917
1496
|
return Math.floor(value);
|
|
918
1497
|
}
|
|
1498
|
+
function normalizeHealthWaitOptions(value, signal) {
|
|
1499
|
+
if (value === false) {
|
|
1500
|
+
return { enabled: false };
|
|
1501
|
+
}
|
|
1502
|
+
if (value === true || value === void 0) {
|
|
1503
|
+
return { enabled: true, signal };
|
|
1504
|
+
}
|
|
1505
|
+
const timeoutMs = typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0 ? Math.floor(value.timeoutMs) : void 0;
|
|
1506
|
+
return {
|
|
1507
|
+
enabled: true,
|
|
1508
|
+
signal,
|
|
1509
|
+
timeoutMs
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
919
1512
|
function normalizeSpawnOptions(spawn, defaultEnabled) {
|
|
920
1513
|
if (spawn === false) {
|
|
921
1514
|
return { enabled: false };
|
|
@@ -939,9 +1532,282 @@ async function readProblem(response) {
|
|
|
939
1532
|
return void 0;
|
|
940
1533
|
}
|
|
941
1534
|
}
|
|
1535
|
+
function normalizeSessionConfigOptions(value) {
|
|
1536
|
+
if (!Array.isArray(value)) {
|
|
1537
|
+
return void 0;
|
|
1538
|
+
}
|
|
1539
|
+
const normalized = value.filter(isSessionConfigOption);
|
|
1540
|
+
return cloneConfigOptions(normalized) ?? [];
|
|
1541
|
+
}
|
|
1542
|
+
function extractConfigOptionsFromSetResponse(response) {
|
|
1543
|
+
if (!isRecord(response)) {
|
|
1544
|
+
return void 0;
|
|
1545
|
+
}
|
|
1546
|
+
return normalizeSessionConfigOptions(response.configOptions);
|
|
1547
|
+
}
|
|
1548
|
+
function findConfigOptionByCategory(options, category) {
|
|
1549
|
+
return options.find((option) => option.category === category);
|
|
1550
|
+
}
|
|
1551
|
+
function findConfigOptionById(options, configId) {
|
|
1552
|
+
return options.find((option) => option.id === configId);
|
|
1553
|
+
}
|
|
1554
|
+
function uniqueCategories(options) {
|
|
1555
|
+
return [...new Set(options.map((option) => option.category).filter((value) => !!value))].sort();
|
|
1556
|
+
}
|
|
1557
|
+
function extractConfigValues(option) {
|
|
1558
|
+
if (!isRecord(option) || option.type !== "select" || !Array.isArray(option.options)) {
|
|
1559
|
+
return [];
|
|
1560
|
+
}
|
|
1561
|
+
const values = [];
|
|
1562
|
+
for (const entry of option.options) {
|
|
1563
|
+
if (isRecord(entry) && typeof entry.value === "string") {
|
|
1564
|
+
values.push(entry.value);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
if (isRecord(entry) && Array.isArray(entry.options)) {
|
|
1568
|
+
for (const nested of entry.options) {
|
|
1569
|
+
if (isRecord(nested) && typeof nested.value === "string") {
|
|
1570
|
+
values.push(nested.value);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return [...new Set(values)];
|
|
1576
|
+
}
|
|
1577
|
+
function extractKnownModeIds(modes) {
|
|
1578
|
+
if (!modes || !Array.isArray(modes.availableModes)) {
|
|
1579
|
+
return [];
|
|
1580
|
+
}
|
|
1581
|
+
return modes.availableModes.map((mode) => typeof mode.id === "string" ? mode.id : null).filter((value) => !!value);
|
|
1582
|
+
}
|
|
1583
|
+
function applyCurrentMode(modes, currentModeId) {
|
|
1584
|
+
if (modes && Array.isArray(modes.availableModes)) {
|
|
1585
|
+
return {
|
|
1586
|
+
...modes,
|
|
1587
|
+
currentModeId
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
currentModeId,
|
|
1592
|
+
availableModes: []
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function applyConfigOptionValue(configOptions, configId, value) {
|
|
1596
|
+
const idx = configOptions.findIndex((o) => o.id === configId);
|
|
1597
|
+
if (idx === -1) {
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
const updated = cloneConfigOptions(configOptions) ?? [];
|
|
1601
|
+
updated[idx] = { ...updated[idx], currentValue: value };
|
|
1602
|
+
return updated;
|
|
1603
|
+
}
|
|
1604
|
+
function envelopeSessionUpdate(message) {
|
|
1605
|
+
if (!isRecord(message) || !("params" in message) || !isRecord(message.params)) {
|
|
1606
|
+
return null;
|
|
1607
|
+
}
|
|
1608
|
+
if (!("update" in message.params) || !isRecord(message.params.update)) {
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
return message.params.update;
|
|
1612
|
+
}
|
|
1613
|
+
function cloneConfigOptions(value) {
|
|
1614
|
+
if (!value) {
|
|
1615
|
+
return void 0;
|
|
1616
|
+
}
|
|
1617
|
+
return JSON.parse(JSON.stringify(value));
|
|
1618
|
+
}
|
|
1619
|
+
function cloneModes(value) {
|
|
1620
|
+
if (!value) {
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
return JSON.parse(JSON.stringify(value));
|
|
1624
|
+
}
|
|
1625
|
+
function isSessionConfigOption(value) {
|
|
1626
|
+
return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.type === "string";
|
|
1627
|
+
}
|
|
1628
|
+
function toTitleCase(input) {
|
|
1629
|
+
if (!input) {
|
|
1630
|
+
return "";
|
|
1631
|
+
}
|
|
1632
|
+
return input.split(/[_\s-]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("");
|
|
1633
|
+
}
|
|
1634
|
+
function formatHealthWaitError(error) {
|
|
1635
|
+
if (error instanceof Error && error.message) {
|
|
1636
|
+
return error.message;
|
|
1637
|
+
}
|
|
1638
|
+
if (error === void 0 || error === null) {
|
|
1639
|
+
return "unknown error";
|
|
1640
|
+
}
|
|
1641
|
+
return String(error);
|
|
1642
|
+
}
|
|
1643
|
+
function anyAbortSignal(signals) {
|
|
1644
|
+
const active = signals.filter((signal) => Boolean(signal));
|
|
1645
|
+
if (active.length === 0) {
|
|
1646
|
+
return void 0;
|
|
1647
|
+
}
|
|
1648
|
+
if (active.length === 1) {
|
|
1649
|
+
return active[0];
|
|
1650
|
+
}
|
|
1651
|
+
const controller = new AbortController();
|
|
1652
|
+
const onAbort = (event) => {
|
|
1653
|
+
cleanup();
|
|
1654
|
+
const signal = event.target;
|
|
1655
|
+
controller.abort(signal.reason ?? createAbortError());
|
|
1656
|
+
};
|
|
1657
|
+
const cleanup = () => {
|
|
1658
|
+
for (const signal of active) {
|
|
1659
|
+
signal.removeEventListener("abort", onAbort);
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
for (const signal of active) {
|
|
1663
|
+
if (signal.aborted) {
|
|
1664
|
+
controller.abort(signal.reason ?? createAbortError());
|
|
1665
|
+
return controller.signal;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
for (const signal of active) {
|
|
1669
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1670
|
+
}
|
|
1671
|
+
return controller.signal;
|
|
1672
|
+
}
|
|
1673
|
+
function throwIfAborted(signal) {
|
|
1674
|
+
if (!signal?.aborted) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
throw signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason);
|
|
1678
|
+
}
|
|
1679
|
+
async function waitForAbortable(promise, signal) {
|
|
1680
|
+
if (!signal) {
|
|
1681
|
+
return promise;
|
|
1682
|
+
}
|
|
1683
|
+
throwIfAborted(signal);
|
|
1684
|
+
return new Promise((resolve, reject) => {
|
|
1685
|
+
const onAbort = () => {
|
|
1686
|
+
cleanup();
|
|
1687
|
+
reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
|
|
1688
|
+
};
|
|
1689
|
+
const cleanup = () => {
|
|
1690
|
+
signal.removeEventListener("abort", onAbort);
|
|
1691
|
+
};
|
|
1692
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1693
|
+
promise.then(
|
|
1694
|
+
(value) => {
|
|
1695
|
+
cleanup();
|
|
1696
|
+
resolve(value);
|
|
1697
|
+
},
|
|
1698
|
+
(error) => {
|
|
1699
|
+
cleanup();
|
|
1700
|
+
reject(error);
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
async function consumeProcessLogSse(body, listener, signal) {
|
|
1706
|
+
const reader = body.getReader();
|
|
1707
|
+
const decoder = new TextDecoder();
|
|
1708
|
+
let buffer = "";
|
|
1709
|
+
try {
|
|
1710
|
+
while (!signal.aborted) {
|
|
1711
|
+
const { done, value } = await reader.read();
|
|
1712
|
+
if (done) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
1716
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
1717
|
+
while (separatorIndex !== -1) {
|
|
1718
|
+
const chunk = buffer.slice(0, separatorIndex);
|
|
1719
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
1720
|
+
const entry = parseProcessLogSseChunk(chunk);
|
|
1721
|
+
if (entry) {
|
|
1722
|
+
listener(entry);
|
|
1723
|
+
}
|
|
1724
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
if (signal.aborted || isAbortError(error)) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
throw error;
|
|
1732
|
+
} finally {
|
|
1733
|
+
reader.releaseLock();
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
function parseProcessLogSseChunk(chunk) {
|
|
1737
|
+
if (!chunk.trim()) {
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
let eventName = "message";
|
|
1741
|
+
const dataLines = [];
|
|
1742
|
+
for (const line of chunk.split("\n")) {
|
|
1743
|
+
if (!line || line.startsWith(":")) {
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
if (line.startsWith("event:")) {
|
|
1747
|
+
eventName = line.slice(6).trim();
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
if (line.startsWith("data:")) {
|
|
1751
|
+
dataLines.push(line.slice(5).trimStart());
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
if (eventName !== "log") {
|
|
1755
|
+
return null;
|
|
1756
|
+
}
|
|
1757
|
+
const data = dataLines.join("\n");
|
|
1758
|
+
if (!data.trim()) {
|
|
1759
|
+
return null;
|
|
1760
|
+
}
|
|
1761
|
+
return JSON.parse(data);
|
|
1762
|
+
}
|
|
1763
|
+
function toWebSocketUrl(url) {
|
|
1764
|
+
const parsed = new URL(url);
|
|
1765
|
+
if (parsed.protocol === "http:") {
|
|
1766
|
+
parsed.protocol = "ws:";
|
|
1767
|
+
} else if (parsed.protocol === "https:") {
|
|
1768
|
+
parsed.protocol = "wss:";
|
|
1769
|
+
}
|
|
1770
|
+
return parsed.toString();
|
|
1771
|
+
}
|
|
1772
|
+
function isAbortError(error) {
|
|
1773
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1774
|
+
}
|
|
1775
|
+
function createAbortError(reason) {
|
|
1776
|
+
if (reason instanceof Error) {
|
|
1777
|
+
return reason;
|
|
1778
|
+
}
|
|
1779
|
+
const message = typeof reason === "string" ? reason : "This operation was aborted.";
|
|
1780
|
+
if (typeof DOMException !== "undefined") {
|
|
1781
|
+
return new DOMException(message, "AbortError");
|
|
1782
|
+
}
|
|
1783
|
+
const error = new Error(message);
|
|
1784
|
+
error.name = "AbortError";
|
|
1785
|
+
return error;
|
|
1786
|
+
}
|
|
1787
|
+
function sleep(ms, signal) {
|
|
1788
|
+
if (!signal) {
|
|
1789
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1790
|
+
}
|
|
1791
|
+
throwIfAborted(signal);
|
|
1792
|
+
return new Promise((resolve, reject) => {
|
|
1793
|
+
const timer = setTimeout(() => {
|
|
1794
|
+
cleanup();
|
|
1795
|
+
resolve();
|
|
1796
|
+
}, ms);
|
|
1797
|
+
const onAbort = () => {
|
|
1798
|
+
cleanup();
|
|
1799
|
+
reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
|
|
1800
|
+
};
|
|
1801
|
+
const cleanup = () => {
|
|
1802
|
+
clearTimeout(timer);
|
|
1803
|
+
signal.removeEventListener("abort", onAbort);
|
|
1804
|
+
};
|
|
1805
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
942
1808
|
|
|
943
1809
|
// src/index.ts
|
|
944
|
-
import { AcpRpcError } from "acp-http-client";
|
|
1810
|
+
import { AcpRpcError as AcpRpcError2 } from "acp-http-client";
|
|
945
1811
|
|
|
946
1812
|
// src/inspector.ts
|
|
947
1813
|
function buildInspectorUrl(options) {
|
|
@@ -957,12 +1823,15 @@ function buildInspectorUrl(options) {
|
|
|
957
1823
|
return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
|
|
958
1824
|
}
|
|
959
1825
|
export {
|
|
960
|
-
AcpRpcError,
|
|
1826
|
+
AcpRpcError2 as AcpRpcError,
|
|
961
1827
|
InMemorySessionPersistDriver,
|
|
962
1828
|
LiveAcpConnection,
|
|
963
1829
|
SandboxAgent,
|
|
964
1830
|
SandboxAgentError,
|
|
965
1831
|
Session,
|
|
1832
|
+
UnsupportedSessionCategoryError,
|
|
1833
|
+
UnsupportedSessionConfigOptionError,
|
|
1834
|
+
UnsupportedSessionValueError,
|
|
966
1835
|
buildInspectorUrl
|
|
967
1836
|
};
|
|
968
1837
|
//# sourceMappingURL=index.js.map
|