sandbox-agent 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +774 -9
- package/dist/index.js +1036 -15
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
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) {
|
|
@@ -125,6 +128,12 @@ var DEFAULT_BASE_URL = "http://sandbox-agent";
|
|
|
125
128
|
var DEFAULT_REPLAY_MAX_EVENTS = 50;
|
|
126
129
|
var DEFAULT_REPLAY_MAX_CHARS = 12e3;
|
|
127
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;
|
|
128
137
|
var SandboxAgentError = class extends Error {
|
|
129
138
|
status;
|
|
130
139
|
problem;
|
|
@@ -137,6 +146,52 @@ var SandboxAgentError = class extends Error {
|
|
|
137
146
|
this.response = response;
|
|
138
147
|
}
|
|
139
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
|
+
};
|
|
140
195
|
var Session = class {
|
|
141
196
|
record;
|
|
142
197
|
sandbox;
|
|
@@ -179,6 +234,32 @@ var Session = class {
|
|
|
179
234
|
const response = await this.send("session/prompt", { prompt });
|
|
180
235
|
return response;
|
|
181
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
|
+
}
|
|
182
263
|
onEvent(listener) {
|
|
183
264
|
return this.sandbox.onSessionEvent(this.id, listener);
|
|
184
265
|
}
|
|
@@ -377,15 +458,163 @@ var LiveAcpConnection = class _LiveAcpConnection {
|
|
|
377
458
|
return this.localByAgentSessionId.get(agentSessionId) ?? null;
|
|
378
459
|
}
|
|
379
460
|
};
|
|
461
|
+
var ProcessTerminalSession = class {
|
|
462
|
+
socket;
|
|
463
|
+
closed;
|
|
464
|
+
readyListeners = /* @__PURE__ */ new Set();
|
|
465
|
+
dataListeners = /* @__PURE__ */ new Set();
|
|
466
|
+
exitListeners = /* @__PURE__ */ new Set();
|
|
467
|
+
errorListeners = /* @__PURE__ */ new Set();
|
|
468
|
+
closeListeners = /* @__PURE__ */ new Set();
|
|
469
|
+
closeSignalSent = false;
|
|
470
|
+
closedResolve;
|
|
471
|
+
constructor(socket) {
|
|
472
|
+
this.socket = socket;
|
|
473
|
+
this.socket.binaryType = "arraybuffer";
|
|
474
|
+
this.closed = new Promise((resolve) => {
|
|
475
|
+
this.closedResolve = resolve;
|
|
476
|
+
});
|
|
477
|
+
this.socket.addEventListener("message", (event) => {
|
|
478
|
+
void this.handleMessage(event.data);
|
|
479
|
+
});
|
|
480
|
+
this.socket.addEventListener("error", () => {
|
|
481
|
+
this.emitError(new Error("Terminal websocket connection failed."));
|
|
482
|
+
});
|
|
483
|
+
this.socket.addEventListener("close", () => {
|
|
484
|
+
this.closedResolve();
|
|
485
|
+
for (const listener of this.closeListeners) {
|
|
486
|
+
listener();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
onReady(listener) {
|
|
491
|
+
this.readyListeners.add(listener);
|
|
492
|
+
return () => {
|
|
493
|
+
this.readyListeners.delete(listener);
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
onData(listener) {
|
|
497
|
+
this.dataListeners.add(listener);
|
|
498
|
+
return () => {
|
|
499
|
+
this.dataListeners.delete(listener);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
onExit(listener) {
|
|
503
|
+
this.exitListeners.add(listener);
|
|
504
|
+
return () => {
|
|
505
|
+
this.exitListeners.delete(listener);
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
onError(listener) {
|
|
509
|
+
this.errorListeners.add(listener);
|
|
510
|
+
return () => {
|
|
511
|
+
this.errorListeners.delete(listener);
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
onClose(listener) {
|
|
515
|
+
this.closeListeners.add(listener);
|
|
516
|
+
return () => {
|
|
517
|
+
this.closeListeners.delete(listener);
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
sendInput(data) {
|
|
521
|
+
const payload = encodeTerminalInput(data);
|
|
522
|
+
this.sendFrame({
|
|
523
|
+
type: "input",
|
|
524
|
+
data: payload.data,
|
|
525
|
+
encoding: payload.encoding
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
resize(payload) {
|
|
529
|
+
this.sendFrame({
|
|
530
|
+
type: "resize",
|
|
531
|
+
cols: payload.cols,
|
|
532
|
+
rows: payload.rows
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
close() {
|
|
536
|
+
if (this.socket.readyState === WS_READY_STATE_CONNECTING) {
|
|
537
|
+
this.socket.addEventListener(
|
|
538
|
+
"open",
|
|
539
|
+
() => {
|
|
540
|
+
this.close();
|
|
541
|
+
},
|
|
542
|
+
{ once: true }
|
|
543
|
+
);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (this.socket.readyState === WS_READY_STATE_OPEN) {
|
|
547
|
+
if (!this.closeSignalSent) {
|
|
548
|
+
this.closeSignalSent = true;
|
|
549
|
+
this.sendFrame({ type: "close" });
|
|
550
|
+
}
|
|
551
|
+
this.socket.close();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (this.socket.readyState !== WS_READY_STATE_CLOSED) {
|
|
555
|
+
this.socket.close();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async handleMessage(data) {
|
|
559
|
+
try {
|
|
560
|
+
if (typeof data === "string") {
|
|
561
|
+
const frame = parseProcessTerminalServerFrame(data);
|
|
562
|
+
if (!frame) {
|
|
563
|
+
this.emitError(new Error("Received invalid terminal control frame."));
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (frame.type === "ready") {
|
|
567
|
+
for (const listener of this.readyListeners) {
|
|
568
|
+
listener(frame);
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (frame.type === "exit") {
|
|
573
|
+
for (const listener of this.exitListeners) {
|
|
574
|
+
listener(frame);
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
this.emitError(frame);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const bytes = await decodeTerminalBytes(data);
|
|
582
|
+
for (const listener of this.dataListeners) {
|
|
583
|
+
listener(bytes);
|
|
584
|
+
}
|
|
585
|
+
} catch (error) {
|
|
586
|
+
this.emitError(error instanceof Error ? error : new Error(String(error)));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
sendFrame(frame) {
|
|
590
|
+
if (this.socket.readyState !== WS_READY_STATE_OPEN) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
this.socket.send(JSON.stringify(frame));
|
|
594
|
+
}
|
|
595
|
+
emitError(error) {
|
|
596
|
+
for (const listener of this.errorListeners) {
|
|
597
|
+
listener(error);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
var WS_READY_STATE_CONNECTING = 0;
|
|
602
|
+
var WS_READY_STATE_OPEN = 1;
|
|
603
|
+
var WS_READY_STATE_CLOSED = 3;
|
|
380
604
|
var SandboxAgent = class _SandboxAgent {
|
|
381
605
|
baseUrl;
|
|
382
606
|
token;
|
|
383
607
|
fetcher;
|
|
384
608
|
defaultHeaders;
|
|
609
|
+
healthWait;
|
|
610
|
+
healthWaitAbortController = new AbortController();
|
|
385
611
|
persist;
|
|
386
612
|
replayMaxEvents;
|
|
387
613
|
replayMaxChars;
|
|
388
614
|
spawnHandle;
|
|
615
|
+
healthPromise;
|
|
616
|
+
healthError;
|
|
617
|
+
disposed = false;
|
|
389
618
|
liveConnections = /* @__PURE__ */ new Map();
|
|
390
619
|
pendingLiveConnections = /* @__PURE__ */ new Map();
|
|
391
620
|
sessionHandles = /* @__PURE__ */ new Map();
|
|
@@ -405,9 +634,11 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
405
634
|
}
|
|
406
635
|
this.fetcher = resolvedFetch;
|
|
407
636
|
this.defaultHeaders = options.headers;
|
|
637
|
+
this.healthWait = normalizeHealthWaitOptions(options.waitForHealth, options.signal);
|
|
408
638
|
this.persist = options.persist ?? new InMemorySessionPersistDriver();
|
|
409
639
|
this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
|
|
410
640
|
this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
|
|
641
|
+
this.startHealthWait();
|
|
411
642
|
}
|
|
412
643
|
static async connect(options) {
|
|
413
644
|
return new _SandboxAgent(options);
|
|
@@ -425,6 +656,7 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
425
656
|
token: handle.token,
|
|
426
657
|
fetch: options.fetch,
|
|
427
658
|
headers: options.headers,
|
|
659
|
+
waitForHealth: false,
|
|
428
660
|
persist: options.persist,
|
|
429
661
|
replayMaxEvents: options.replayMaxEvents,
|
|
430
662
|
replayMaxChars: options.replayMaxChars
|
|
@@ -433,6 +665,8 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
433
665
|
return client;
|
|
434
666
|
}
|
|
435
667
|
async dispose() {
|
|
668
|
+
this.disposed = true;
|
|
669
|
+
this.healthWaitAbortController.abort(createAbortError("SandboxAgent was disposed."));
|
|
436
670
|
const connections = [...this.liveConnections.values()];
|
|
437
671
|
this.liveConnections.clear();
|
|
438
672
|
const pending = [...this.pendingLiveConnections.values()];
|
|
@@ -484,12 +718,32 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
484
718
|
agentSessionId: response.sessionId,
|
|
485
719
|
lastConnectionId: live.connectionId,
|
|
486
720
|
createdAt: nowMs(),
|
|
487
|
-
sessionInit
|
|
721
|
+
sessionInit,
|
|
722
|
+
configOptions: cloneConfigOptions(response.configOptions),
|
|
723
|
+
modes: cloneModes(response.modes)
|
|
488
724
|
};
|
|
489
725
|
await this.persist.updateSession(record);
|
|
490
726
|
this.nextSessionEventIndexBySession.set(record.id, 1);
|
|
491
727
|
live.bindSession(record.id, record.agentSessionId);
|
|
492
|
-
|
|
728
|
+
let session = this.upsertSessionHandle(record);
|
|
729
|
+
try {
|
|
730
|
+
if (request.mode) {
|
|
731
|
+
session = (await this.setSessionMode(session.id, request.mode)).session;
|
|
732
|
+
}
|
|
733
|
+
if (request.model) {
|
|
734
|
+
session = (await this.setSessionModel(session.id, request.model)).session;
|
|
735
|
+
}
|
|
736
|
+
if (request.thoughtLevel) {
|
|
737
|
+
session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
|
|
738
|
+
}
|
|
739
|
+
} catch (err) {
|
|
740
|
+
try {
|
|
741
|
+
await this.destroySession(session.id);
|
|
742
|
+
} catch {
|
|
743
|
+
}
|
|
744
|
+
throw err;
|
|
745
|
+
}
|
|
746
|
+
return session;
|
|
493
747
|
}
|
|
494
748
|
async resumeSession(id) {
|
|
495
749
|
const existing = await this.persist.getSession(id);
|
|
@@ -507,7 +761,9 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
507
761
|
...existing,
|
|
508
762
|
agentSessionId: recreated.sessionId,
|
|
509
763
|
lastConnectionId: live.connectionId,
|
|
510
|
-
destroyedAt: void 0
|
|
764
|
+
destroyedAt: void 0,
|
|
765
|
+
configOptions: cloneConfigOptions(recreated.configOptions),
|
|
766
|
+
modes: cloneModes(recreated.modes)
|
|
511
767
|
};
|
|
512
768
|
await this.persist.updateSession(updated);
|
|
513
769
|
live.bindSession(updated.id, updated.agentSessionId);
|
|
@@ -517,15 +773,26 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
517
773
|
async resumeOrCreateSession(request) {
|
|
518
774
|
const existing = await this.persist.getSession(request.id);
|
|
519
775
|
if (existing) {
|
|
520
|
-
|
|
776
|
+
let session = await this.resumeSession(existing.id);
|
|
777
|
+
if (request.mode) {
|
|
778
|
+
session = (await this.setSessionMode(session.id, request.mode)).session;
|
|
779
|
+
}
|
|
780
|
+
if (request.model) {
|
|
781
|
+
session = (await this.setSessionModel(session.id, request.model)).session;
|
|
782
|
+
}
|
|
783
|
+
if (request.thoughtLevel) {
|
|
784
|
+
session = (await this.setSessionThoughtLevel(session.id, request.thoughtLevel)).session;
|
|
785
|
+
}
|
|
786
|
+
return session;
|
|
521
787
|
}
|
|
522
788
|
return this.createSession(request);
|
|
523
789
|
}
|
|
524
790
|
async destroySession(id) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
791
|
+
try {
|
|
792
|
+
await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true);
|
|
793
|
+
} catch {
|
|
528
794
|
}
|
|
795
|
+
const existing = await this.requireSessionRecord(id);
|
|
529
796
|
const updated = {
|
|
530
797
|
...existing,
|
|
531
798
|
destroyedAt: nowMs()
|
|
@@ -533,7 +800,132 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
533
800
|
await this.persist.updateSession(updated);
|
|
534
801
|
return this.upsertSessionHandle(updated);
|
|
535
802
|
}
|
|
803
|
+
async setSessionMode(sessionId, modeId) {
|
|
804
|
+
const mode = modeId.trim();
|
|
805
|
+
if (!mode) {
|
|
806
|
+
throw new Error("setSessionMode requires a non-empty modeId");
|
|
807
|
+
}
|
|
808
|
+
const record = await this.requireSessionRecord(sessionId);
|
|
809
|
+
const knownModeIds = extractKnownModeIds(record.modes);
|
|
810
|
+
if (knownModeIds.length > 0 && !knownModeIds.includes(mode)) {
|
|
811
|
+
throw new UnsupportedSessionValueError(sessionId, "mode", "mode", mode, knownModeIds);
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
return await this.sendSessionMethodInternal(
|
|
815
|
+
sessionId,
|
|
816
|
+
"session/set_mode",
|
|
817
|
+
{ modeId: mode },
|
|
818
|
+
{},
|
|
819
|
+
false
|
|
820
|
+
);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (!(error instanceof AcpRpcError) || error.code !== -32601) {
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
return this.setSessionCategoryValue(sessionId, "mode", mode);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
async setSessionConfigOption(sessionId, configId, value) {
|
|
829
|
+
const resolvedConfigId = configId.trim();
|
|
830
|
+
if (!resolvedConfigId) {
|
|
831
|
+
throw new Error("setSessionConfigOption requires a non-empty configId");
|
|
832
|
+
}
|
|
833
|
+
const resolvedValue = value.trim();
|
|
834
|
+
if (!resolvedValue) {
|
|
835
|
+
throw new Error("setSessionConfigOption requires a non-empty value");
|
|
836
|
+
}
|
|
837
|
+
const options = await this.getSessionConfigOptions(sessionId);
|
|
838
|
+
const option = findConfigOptionById(options, resolvedConfigId);
|
|
839
|
+
if (!option) {
|
|
840
|
+
throw new UnsupportedSessionConfigOptionError(
|
|
841
|
+
sessionId,
|
|
842
|
+
resolvedConfigId,
|
|
843
|
+
options.map((item) => item.id)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
const allowedValues = extractConfigValues(option);
|
|
847
|
+
if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
|
|
848
|
+
throw new UnsupportedSessionValueError(
|
|
849
|
+
sessionId,
|
|
850
|
+
option.category ?? "uncategorized",
|
|
851
|
+
option.id,
|
|
852
|
+
resolvedValue,
|
|
853
|
+
allowedValues
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
return await this.sendSessionMethodInternal(
|
|
857
|
+
sessionId,
|
|
858
|
+
"session/set_config_option",
|
|
859
|
+
{
|
|
860
|
+
configId: resolvedConfigId,
|
|
861
|
+
value: resolvedValue
|
|
862
|
+
},
|
|
863
|
+
{},
|
|
864
|
+
false
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
async setSessionModel(sessionId, model) {
|
|
868
|
+
return this.setSessionCategoryValue(sessionId, "model", model);
|
|
869
|
+
}
|
|
870
|
+
async setSessionThoughtLevel(sessionId, thoughtLevel) {
|
|
871
|
+
return this.setSessionCategoryValue(sessionId, "thought_level", thoughtLevel);
|
|
872
|
+
}
|
|
873
|
+
async getSessionConfigOptions(sessionId) {
|
|
874
|
+
const record = await this.requireSessionRecord(sessionId);
|
|
875
|
+
const hydrated = await this.hydrateSessionConfigOptions(record.id, record);
|
|
876
|
+
return cloneConfigOptions(hydrated.configOptions) ?? [];
|
|
877
|
+
}
|
|
878
|
+
async getSessionModes(sessionId) {
|
|
879
|
+
const record = await this.requireSessionRecord(sessionId);
|
|
880
|
+
return cloneModes(record.modes);
|
|
881
|
+
}
|
|
882
|
+
async setSessionCategoryValue(sessionId, category, value) {
|
|
883
|
+
const resolvedValue = value.trim();
|
|
884
|
+
if (!resolvedValue) {
|
|
885
|
+
throw new Error(`setSession${toTitleCase(category)} requires a non-empty value`);
|
|
886
|
+
}
|
|
887
|
+
const options = await this.getSessionConfigOptions(sessionId);
|
|
888
|
+
const option = findConfigOptionByCategory(options, category);
|
|
889
|
+
if (!option) {
|
|
890
|
+
const categories = uniqueCategories(options);
|
|
891
|
+
throw new UnsupportedSessionCategoryError(sessionId, category, categories);
|
|
892
|
+
}
|
|
893
|
+
const allowedValues = extractConfigValues(option);
|
|
894
|
+
if (allowedValues.length > 0 && !allowedValues.includes(resolvedValue)) {
|
|
895
|
+
throw new UnsupportedSessionValueError(
|
|
896
|
+
sessionId,
|
|
897
|
+
category,
|
|
898
|
+
option.id,
|
|
899
|
+
resolvedValue,
|
|
900
|
+
allowedValues
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
return this.setSessionConfigOption(sessionId, option.id, resolvedValue);
|
|
904
|
+
}
|
|
905
|
+
async hydrateSessionConfigOptions(sessionId, snapshot) {
|
|
906
|
+
if (snapshot.configOptions !== void 0) {
|
|
907
|
+
return snapshot;
|
|
908
|
+
}
|
|
909
|
+
const info = await this.getAgent(snapshot.agent, { config: true });
|
|
910
|
+
const configOptions = normalizeSessionConfigOptions(info.configOptions) ?? [];
|
|
911
|
+
const record = await this.persist.getSession(sessionId);
|
|
912
|
+
if (!record) {
|
|
913
|
+
return { ...snapshot, configOptions };
|
|
914
|
+
}
|
|
915
|
+
const updated = {
|
|
916
|
+
...record,
|
|
917
|
+
configOptions
|
|
918
|
+
};
|
|
919
|
+
await this.persist.updateSession(updated);
|
|
920
|
+
return updated;
|
|
921
|
+
}
|
|
536
922
|
async sendSessionMethod(sessionId, method, params, options = {}) {
|
|
923
|
+
return this.sendSessionMethodInternal(sessionId, method, params, options, false);
|
|
924
|
+
}
|
|
925
|
+
async sendSessionMethodInternal(sessionId, method, params, options, allowManagedCancel) {
|
|
926
|
+
if (method === SESSION_CANCEL_METHOD && !allowManagedCancel) {
|
|
927
|
+
throw new Error(MANUAL_CANCEL_ERROR);
|
|
928
|
+
}
|
|
537
929
|
const record = await this.persist.getSession(sessionId);
|
|
538
930
|
if (!record) {
|
|
539
931
|
throw new Error(`session '${sessionId}' not found`);
|
|
@@ -541,15 +933,73 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
541
933
|
const live = await this.getLiveConnection(record.agent);
|
|
542
934
|
if (!live.hasBoundSession(record.id, record.agentSessionId)) {
|
|
543
935
|
const restored = await this.resumeSession(record.id);
|
|
544
|
-
return this.
|
|
936
|
+
return this.sendSessionMethodInternal(restored.id, method, params, options, allowManagedCancel);
|
|
545
937
|
}
|
|
546
938
|
const response = await live.sendSessionMethod(record.id, method, params, options);
|
|
939
|
+
await this.persistSessionStateFromMethod(record.id, method, params, response);
|
|
547
940
|
const refreshed = await this.requireSessionRecord(record.id);
|
|
548
941
|
return {
|
|
549
942
|
session: this.upsertSessionHandle(refreshed),
|
|
550
943
|
response
|
|
551
944
|
};
|
|
552
945
|
}
|
|
946
|
+
async persistSessionStateFromMethod(sessionId, method, params, response) {
|
|
947
|
+
const record = await this.persist.getSession(sessionId);
|
|
948
|
+
if (!record) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (method === "session/set_config_option") {
|
|
952
|
+
const configId = typeof params.configId === "string" ? params.configId : null;
|
|
953
|
+
const value = typeof params.value === "string" ? params.value : null;
|
|
954
|
+
const updates = {};
|
|
955
|
+
const serverConfigOptions = extractConfigOptionsFromSetResponse(response);
|
|
956
|
+
if (serverConfigOptions) {
|
|
957
|
+
updates.configOptions = cloneConfigOptions(serverConfigOptions);
|
|
958
|
+
} else if (record.configOptions && configId && value) {
|
|
959
|
+
const updated = applyConfigOptionValue(record.configOptions, configId, value);
|
|
960
|
+
if (updated) {
|
|
961
|
+
updates.configOptions = updated;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (configId && value) {
|
|
965
|
+
const source = updates.configOptions ?? record.configOptions;
|
|
966
|
+
const option = source ? findConfigOptionById(source, configId) : null;
|
|
967
|
+
if (option?.category === "mode") {
|
|
968
|
+
const nextModes = applyCurrentMode(record.modes, value);
|
|
969
|
+
if (nextModes) {
|
|
970
|
+
updates.modes = nextModes;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (Object.keys(updates).length > 0) {
|
|
975
|
+
await this.persist.updateSession({ ...record, ...updates });
|
|
976
|
+
}
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (method === "session/set_mode") {
|
|
980
|
+
const modeId = typeof params.modeId === "string" ? params.modeId : null;
|
|
981
|
+
if (!modeId) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const updates = {};
|
|
985
|
+
const nextModes = applyCurrentMode(record.modes, modeId);
|
|
986
|
+
if (nextModes) {
|
|
987
|
+
updates.modes = nextModes;
|
|
988
|
+
}
|
|
989
|
+
if (record.configOptions) {
|
|
990
|
+
const modeOption = findConfigOptionByCategory(record.configOptions, "mode");
|
|
991
|
+
if (modeOption) {
|
|
992
|
+
const updated = applyConfigOptionValue(record.configOptions, modeOption.id, modeId);
|
|
993
|
+
if (updated) {
|
|
994
|
+
updates.configOptions = updated;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (Object.keys(updates).length > 0) {
|
|
999
|
+
await this.persist.updateSession({ ...record, ...updates });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
553
1003
|
onSessionEvent(sessionId, listener) {
|
|
554
1004
|
const listeners = this.eventListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
555
1005
|
listeners.add(listener);
|
|
@@ -566,16 +1016,16 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
566
1016
|
};
|
|
567
1017
|
}
|
|
568
1018
|
async getHealth() {
|
|
569
|
-
return this.
|
|
1019
|
+
return this.requestHealth();
|
|
570
1020
|
}
|
|
571
1021
|
async listAgents(options) {
|
|
572
1022
|
return this.requestJson("GET", `${API_PREFIX}/agents`, {
|
|
573
|
-
query: options
|
|
1023
|
+
query: toAgentQuery(options)
|
|
574
1024
|
});
|
|
575
1025
|
}
|
|
576
1026
|
async getAgent(agent, options) {
|
|
577
1027
|
return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
|
|
578
|
-
query: options
|
|
1028
|
+
query: toAgentQuery(options)
|
|
579
1029
|
});
|
|
580
1030
|
}
|
|
581
1031
|
async installAgent(agent, request = {}) {
|
|
@@ -647,7 +1097,107 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
647
1097
|
async deleteSkillsConfig(query) {
|
|
648
1098
|
await this.requestRaw("DELETE", `${API_PREFIX}/config/skills`, { query });
|
|
649
1099
|
}
|
|
1100
|
+
async getProcessConfig() {
|
|
1101
|
+
return this.requestJson("GET", `${API_PREFIX}/processes/config`);
|
|
1102
|
+
}
|
|
1103
|
+
async setProcessConfig(config) {
|
|
1104
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/config`, {
|
|
1105
|
+
body: config
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
async createProcess(request) {
|
|
1109
|
+
return this.requestJson("POST", `${API_PREFIX}/processes`, {
|
|
1110
|
+
body: request
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
async runProcess(request) {
|
|
1114
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/run`, {
|
|
1115
|
+
body: request
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
async listProcesses() {
|
|
1119
|
+
return this.requestJson("GET", `${API_PREFIX}/processes`);
|
|
1120
|
+
}
|
|
1121
|
+
async getProcess(id) {
|
|
1122
|
+
return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
|
|
1123
|
+
}
|
|
1124
|
+
async stopProcess(id, query) {
|
|
1125
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/stop`, {
|
|
1126
|
+
query
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
async killProcess(id, query) {
|
|
1130
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/kill`, {
|
|
1131
|
+
query
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
async deleteProcess(id) {
|
|
1135
|
+
await this.requestRaw("DELETE", `${API_PREFIX}/processes/${encodeURIComponent(id)}`);
|
|
1136
|
+
}
|
|
1137
|
+
async getProcessLogs(id, query = {}) {
|
|
1138
|
+
return this.requestJson("GET", `${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`, {
|
|
1139
|
+
query
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
async followProcessLogs(id, listener, query = {}) {
|
|
1143
|
+
const abortController = new AbortController();
|
|
1144
|
+
const response = await this.requestRaw(
|
|
1145
|
+
"GET",
|
|
1146
|
+
`${API_PREFIX}/processes/${encodeURIComponent(id)}/logs`,
|
|
1147
|
+
{
|
|
1148
|
+
query: { ...query, follow: true },
|
|
1149
|
+
accept: "text/event-stream",
|
|
1150
|
+
signal: abortController.signal
|
|
1151
|
+
}
|
|
1152
|
+
);
|
|
1153
|
+
if (!response.body) {
|
|
1154
|
+
abortController.abort();
|
|
1155
|
+
throw new Error("SSE stream is not readable in this environment.");
|
|
1156
|
+
}
|
|
1157
|
+
const closed = consumeProcessLogSse(response.body, listener, abortController.signal);
|
|
1158
|
+
return {
|
|
1159
|
+
close: () => abortController.abort(),
|
|
1160
|
+
closed
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
async sendProcessInput(id, request) {
|
|
1164
|
+
return this.requestJson("POST", `${API_PREFIX}/processes/${encodeURIComponent(id)}/input`, {
|
|
1165
|
+
body: request
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async resizeProcessTerminal(id, request) {
|
|
1169
|
+
return this.requestJson(
|
|
1170
|
+
"POST",
|
|
1171
|
+
`${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/resize`,
|
|
1172
|
+
{
|
|
1173
|
+
body: request
|
|
1174
|
+
}
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
buildProcessTerminalWebSocketUrl(id, options = {}) {
|
|
1178
|
+
return toWebSocketUrl(
|
|
1179
|
+
this.buildUrl(`${API_PREFIX}/processes/${encodeURIComponent(id)}/terminal/ws`, {
|
|
1180
|
+
access_token: options.accessToken ?? this.token
|
|
1181
|
+
})
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
connectProcessTerminalWebSocket(id, options = {}) {
|
|
1185
|
+
const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket;
|
|
1186
|
+
if (!WebSocketCtor) {
|
|
1187
|
+
throw new Error("WebSocket API is not available; provide a WebSocket implementation.");
|
|
1188
|
+
}
|
|
1189
|
+
return new WebSocketCtor(
|
|
1190
|
+
this.buildProcessTerminalWebSocketUrl(id, {
|
|
1191
|
+
accessToken: options.accessToken
|
|
1192
|
+
}),
|
|
1193
|
+
options.protocols
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
connectProcessTerminal(id, options = {}) {
|
|
1197
|
+
return new ProcessTerminalSession(this.connectProcessTerminalWebSocket(id, options));
|
|
1198
|
+
}
|
|
650
1199
|
async getLiveConnection(agent) {
|
|
1200
|
+
await this.awaitHealthy();
|
|
651
1201
|
const existing = this.liveConnections.get(agent);
|
|
652
1202
|
if (existing) {
|
|
653
1203
|
return existing;
|
|
@@ -700,6 +1250,7 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
700
1250
|
payload: cloneEnvelope(envelope)
|
|
701
1251
|
};
|
|
702
1252
|
await this.persist.insertEvent(event);
|
|
1253
|
+
await this.persistSessionStateFromEvent(localSessionId, envelope, direction);
|
|
703
1254
|
const listeners = this.eventListeners.get(localSessionId);
|
|
704
1255
|
if (!listeners || listeners.size === 0) {
|
|
705
1256
|
return;
|
|
@@ -708,6 +1259,46 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
708
1259
|
listener(event);
|
|
709
1260
|
}
|
|
710
1261
|
}
|
|
1262
|
+
async persistSessionStateFromEvent(sessionId, envelope, direction) {
|
|
1263
|
+
if (direction !== "inbound") {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (envelopeMethod(envelope) !== "session/update") {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const update = envelopeSessionUpdate(envelope);
|
|
1270
|
+
if (!update || typeof update.sessionUpdate !== "string") {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const record = await this.persist.getSession(sessionId);
|
|
1274
|
+
if (!record) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (update.sessionUpdate === "config_option_update") {
|
|
1278
|
+
const configOptions = normalizeSessionConfigOptions(update.configOptions);
|
|
1279
|
+
if (configOptions) {
|
|
1280
|
+
await this.persist.updateSession({
|
|
1281
|
+
...record,
|
|
1282
|
+
configOptions
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (update.sessionUpdate === "current_mode_update") {
|
|
1288
|
+
const modeId = typeof update.currentModeId === "string" ? update.currentModeId : null;
|
|
1289
|
+
if (!modeId) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const nextModes = applyCurrentMode(record.modes, modeId);
|
|
1293
|
+
if (!nextModes) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
await this.persist.updateSession({
|
|
1297
|
+
...record,
|
|
1298
|
+
modes: nextModes
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
711
1302
|
async allocateSessionEventIndex(sessionId) {
|
|
712
1303
|
await this.ensureSessionEventIndexSeeded(sessionId);
|
|
713
1304
|
const nextIndex = this.nextSessionEventIndexBySession.get(sessionId) ?? 1;
|
|
@@ -793,7 +1384,8 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
793
1384
|
body: options.body,
|
|
794
1385
|
headers: options.headers,
|
|
795
1386
|
accept: options.accept ?? "application/json",
|
|
796
|
-
signal: options.signal
|
|
1387
|
+
signal: options.signal,
|
|
1388
|
+
skipReadyWait: options.skipReadyWait
|
|
797
1389
|
});
|
|
798
1390
|
if (response.status === 204) {
|
|
799
1391
|
return void 0;
|
|
@@ -801,6 +1393,9 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
801
1393
|
return await response.json();
|
|
802
1394
|
}
|
|
803
1395
|
async requestRaw(method, path, options = {}) {
|
|
1396
|
+
if (!options.skipReadyWait) {
|
|
1397
|
+
await this.awaitHealthy(options.signal);
|
|
1398
|
+
}
|
|
804
1399
|
const url = this.buildUrl(path, options.query);
|
|
805
1400
|
const headers = this.buildHeaders(options.headers);
|
|
806
1401
|
if (options.accept) {
|
|
@@ -830,6 +1425,64 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
830
1425
|
}
|
|
831
1426
|
return response;
|
|
832
1427
|
}
|
|
1428
|
+
startHealthWait() {
|
|
1429
|
+
if (!this.healthWait.enabled || this.healthPromise) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
this.healthPromise = this.runHealthWait().catch((error) => {
|
|
1433
|
+
this.healthError = error instanceof Error ? error : new Error(String(error));
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
async awaitHealthy(signal) {
|
|
1437
|
+
if (!this.healthPromise) {
|
|
1438
|
+
throwIfAborted(signal);
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
await waitForAbortable(this.healthPromise, signal);
|
|
1442
|
+
throwIfAborted(signal);
|
|
1443
|
+
if (this.healthError) {
|
|
1444
|
+
throw this.healthError;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
async runHealthWait() {
|
|
1448
|
+
const signal = this.healthWait.enabled ? anyAbortSignal([this.healthWait.signal, this.healthWaitAbortController.signal]) : void 0;
|
|
1449
|
+
const startedAt = Date.now();
|
|
1450
|
+
const deadline = typeof this.healthWait.timeoutMs === "number" ? startedAt + this.healthWait.timeoutMs : void 0;
|
|
1451
|
+
let delayMs = HEALTH_WAIT_MIN_DELAY_MS;
|
|
1452
|
+
let nextLogAt = startedAt + HEALTH_WAIT_LOG_AFTER_MS;
|
|
1453
|
+
let lastError;
|
|
1454
|
+
while (!this.disposed && (deadline === void 0 || Date.now() < deadline)) {
|
|
1455
|
+
throwIfAborted(signal);
|
|
1456
|
+
try {
|
|
1457
|
+
const health = await this.requestHealth({ signal });
|
|
1458
|
+
if (health.status === "ok") {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
lastError = new Error(`Unexpected health response: ${JSON.stringify(health)}`);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
if (isAbortError(error)) {
|
|
1464
|
+
throw error;
|
|
1465
|
+
}
|
|
1466
|
+
lastError = error;
|
|
1467
|
+
}
|
|
1468
|
+
const now = Date.now();
|
|
1469
|
+
if (now >= nextLogAt) {
|
|
1470
|
+
const details = formatHealthWaitError(lastError);
|
|
1471
|
+
console.warn(
|
|
1472
|
+
`sandbox-agent at ${this.baseUrl} is not healthy after ${now - startedAt}ms; still waiting (${details})`
|
|
1473
|
+
);
|
|
1474
|
+
nextLogAt = now + HEALTH_WAIT_LOG_EVERY_MS;
|
|
1475
|
+
}
|
|
1476
|
+
await sleep(delayMs, signal);
|
|
1477
|
+
delayMs = Math.min(HEALTH_WAIT_MAX_DELAY_MS, delayMs * 2);
|
|
1478
|
+
}
|
|
1479
|
+
if (this.disposed) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
throw new Error(
|
|
1483
|
+
`Timed out waiting for sandbox-agent health after ${this.healthWait.timeoutMs}ms (${formatHealthWaitError(lastError)})`
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
833
1486
|
buildHeaders(extra) {
|
|
834
1487
|
const headers = new Headers(this.defaultHeaders ?? void 0);
|
|
835
1488
|
if (this.token) {
|
|
@@ -853,7 +1506,75 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
853
1506
|
}
|
|
854
1507
|
return url.toString();
|
|
855
1508
|
}
|
|
1509
|
+
async requestHealth(options = {}) {
|
|
1510
|
+
return this.requestJson("GET", `${API_PREFIX}/health`, {
|
|
1511
|
+
signal: options.signal,
|
|
1512
|
+
skipReadyWait: true
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
856
1515
|
};
|
|
1516
|
+
function parseProcessTerminalServerFrame(payload) {
|
|
1517
|
+
try {
|
|
1518
|
+
const parsed = JSON.parse(payload);
|
|
1519
|
+
if (!isRecord(parsed) || typeof parsed.type !== "string") {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
if (parsed.type === "ready" && typeof parsed.processId === "string") {
|
|
1523
|
+
return parsed;
|
|
1524
|
+
}
|
|
1525
|
+
if (parsed.type === "exit" && (parsed.exitCode === void 0 || parsed.exitCode === null || typeof parsed.exitCode === "number")) {
|
|
1526
|
+
return parsed;
|
|
1527
|
+
}
|
|
1528
|
+
if (parsed.type === "error" && typeof parsed.message === "string") {
|
|
1529
|
+
return parsed;
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
return null;
|
|
1533
|
+
}
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
function encodeTerminalInput(data) {
|
|
1537
|
+
if (typeof data === "string") {
|
|
1538
|
+
return { data };
|
|
1539
|
+
}
|
|
1540
|
+
const bytes = encodeTerminalBytes(data);
|
|
1541
|
+
return {
|
|
1542
|
+
data: bytesToBase64(bytes),
|
|
1543
|
+
encoding: "base64"
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function encodeTerminalBytes(data) {
|
|
1547
|
+
if (data instanceof ArrayBuffer) {
|
|
1548
|
+
return new Uint8Array(data);
|
|
1549
|
+
}
|
|
1550
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
|
|
1551
|
+
}
|
|
1552
|
+
async function decodeTerminalBytes(data) {
|
|
1553
|
+
if (data instanceof ArrayBuffer) {
|
|
1554
|
+
return new Uint8Array(data);
|
|
1555
|
+
}
|
|
1556
|
+
if (ArrayBuffer.isView(data)) {
|
|
1557
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
|
|
1558
|
+
}
|
|
1559
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
1560
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
1561
|
+
}
|
|
1562
|
+
throw new Error(`Unsupported terminal frame payload: ${String(data)}`);
|
|
1563
|
+
}
|
|
1564
|
+
function bytesToBase64(bytes) {
|
|
1565
|
+
if (typeof Buffer !== "undefined") {
|
|
1566
|
+
return Buffer.from(bytes).toString("base64");
|
|
1567
|
+
}
|
|
1568
|
+
if (typeof btoa === "function") {
|
|
1569
|
+
let binary = "";
|
|
1570
|
+
const chunkSize = 32768;
|
|
1571
|
+
for (let index = 0; index < bytes.length; index += chunkSize) {
|
|
1572
|
+
binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
|
|
1573
|
+
}
|
|
1574
|
+
return btoa(binary);
|
|
1575
|
+
}
|
|
1576
|
+
throw new Error("Base64 encoding is not available in this environment.");
|
|
1577
|
+
}
|
|
857
1578
|
async function autoAuthenticate(acp, methods) {
|
|
858
1579
|
const envBased = methods.find(
|
|
859
1580
|
(m) => m.id === "codex-api-key" || m.id === "openai-api-key" || m.id === "anthropic-api-key"
|
|
@@ -866,6 +1587,15 @@ async function autoAuthenticate(acp, methods) {
|
|
|
866
1587
|
} catch {
|
|
867
1588
|
}
|
|
868
1589
|
}
|
|
1590
|
+
function toAgentQuery(options) {
|
|
1591
|
+
if (!options) {
|
|
1592
|
+
return void 0;
|
|
1593
|
+
}
|
|
1594
|
+
return {
|
|
1595
|
+
config: options.config,
|
|
1596
|
+
no_cache: options.noCache
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
869
1599
|
function normalizeSessionInit(value) {
|
|
870
1600
|
if (!value) {
|
|
871
1601
|
return {
|
|
@@ -973,6 +1703,20 @@ function normalizePositiveInt(value, fallback) {
|
|
|
973
1703
|
}
|
|
974
1704
|
return Math.floor(value);
|
|
975
1705
|
}
|
|
1706
|
+
function normalizeHealthWaitOptions(value, signal) {
|
|
1707
|
+
if (value === false) {
|
|
1708
|
+
return { enabled: false };
|
|
1709
|
+
}
|
|
1710
|
+
if (value === true || value === void 0) {
|
|
1711
|
+
return { enabled: true, signal };
|
|
1712
|
+
}
|
|
1713
|
+
const timeoutMs = typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0 ? Math.floor(value.timeoutMs) : void 0;
|
|
1714
|
+
return {
|
|
1715
|
+
enabled: true,
|
|
1716
|
+
signal,
|
|
1717
|
+
timeoutMs
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
976
1720
|
function normalizeSpawnOptions(spawn, defaultEnabled) {
|
|
977
1721
|
if (spawn === false) {
|
|
978
1722
|
return { enabled: false };
|
|
@@ -996,9 +1740,282 @@ async function readProblem(response) {
|
|
|
996
1740
|
return void 0;
|
|
997
1741
|
}
|
|
998
1742
|
}
|
|
1743
|
+
function normalizeSessionConfigOptions(value) {
|
|
1744
|
+
if (!Array.isArray(value)) {
|
|
1745
|
+
return void 0;
|
|
1746
|
+
}
|
|
1747
|
+
const normalized = value.filter(isSessionConfigOption);
|
|
1748
|
+
return cloneConfigOptions(normalized) ?? [];
|
|
1749
|
+
}
|
|
1750
|
+
function extractConfigOptionsFromSetResponse(response) {
|
|
1751
|
+
if (!isRecord(response)) {
|
|
1752
|
+
return void 0;
|
|
1753
|
+
}
|
|
1754
|
+
return normalizeSessionConfigOptions(response.configOptions);
|
|
1755
|
+
}
|
|
1756
|
+
function findConfigOptionByCategory(options, category) {
|
|
1757
|
+
return options.find((option) => option.category === category);
|
|
1758
|
+
}
|
|
1759
|
+
function findConfigOptionById(options, configId) {
|
|
1760
|
+
return options.find((option) => option.id === configId);
|
|
1761
|
+
}
|
|
1762
|
+
function uniqueCategories(options) {
|
|
1763
|
+
return [...new Set(options.map((option) => option.category).filter((value) => !!value))].sort();
|
|
1764
|
+
}
|
|
1765
|
+
function extractConfigValues(option) {
|
|
1766
|
+
if (!isRecord(option) || option.type !== "select" || !Array.isArray(option.options)) {
|
|
1767
|
+
return [];
|
|
1768
|
+
}
|
|
1769
|
+
const values = [];
|
|
1770
|
+
for (const entry of option.options) {
|
|
1771
|
+
if (isRecord(entry) && typeof entry.value === "string") {
|
|
1772
|
+
values.push(entry.value);
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
if (isRecord(entry) && Array.isArray(entry.options)) {
|
|
1776
|
+
for (const nested of entry.options) {
|
|
1777
|
+
if (isRecord(nested) && typeof nested.value === "string") {
|
|
1778
|
+
values.push(nested.value);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return [...new Set(values)];
|
|
1784
|
+
}
|
|
1785
|
+
function extractKnownModeIds(modes) {
|
|
1786
|
+
if (!modes || !Array.isArray(modes.availableModes)) {
|
|
1787
|
+
return [];
|
|
1788
|
+
}
|
|
1789
|
+
return modes.availableModes.map((mode) => typeof mode.id === "string" ? mode.id : null).filter((value) => !!value);
|
|
1790
|
+
}
|
|
1791
|
+
function applyCurrentMode(modes, currentModeId) {
|
|
1792
|
+
if (modes && Array.isArray(modes.availableModes)) {
|
|
1793
|
+
return {
|
|
1794
|
+
...modes,
|
|
1795
|
+
currentModeId
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
return {
|
|
1799
|
+
currentModeId,
|
|
1800
|
+
availableModes: []
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
function applyConfigOptionValue(configOptions, configId, value) {
|
|
1804
|
+
const idx = configOptions.findIndex((o) => o.id === configId);
|
|
1805
|
+
if (idx === -1) {
|
|
1806
|
+
return null;
|
|
1807
|
+
}
|
|
1808
|
+
const updated = cloneConfigOptions(configOptions) ?? [];
|
|
1809
|
+
updated[idx] = { ...updated[idx], currentValue: value };
|
|
1810
|
+
return updated;
|
|
1811
|
+
}
|
|
1812
|
+
function envelopeSessionUpdate(message) {
|
|
1813
|
+
if (!isRecord(message) || !("params" in message) || !isRecord(message.params)) {
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
if (!("update" in message.params) || !isRecord(message.params.update)) {
|
|
1817
|
+
return null;
|
|
1818
|
+
}
|
|
1819
|
+
return message.params.update;
|
|
1820
|
+
}
|
|
1821
|
+
function cloneConfigOptions(value) {
|
|
1822
|
+
if (!value) {
|
|
1823
|
+
return void 0;
|
|
1824
|
+
}
|
|
1825
|
+
return JSON.parse(JSON.stringify(value));
|
|
1826
|
+
}
|
|
1827
|
+
function cloneModes(value) {
|
|
1828
|
+
if (!value) {
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
return JSON.parse(JSON.stringify(value));
|
|
1832
|
+
}
|
|
1833
|
+
function isSessionConfigOption(value) {
|
|
1834
|
+
return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.type === "string";
|
|
1835
|
+
}
|
|
1836
|
+
function toTitleCase(input) {
|
|
1837
|
+
if (!input) {
|
|
1838
|
+
return "";
|
|
1839
|
+
}
|
|
1840
|
+
return input.split(/[_\s-]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("");
|
|
1841
|
+
}
|
|
1842
|
+
function formatHealthWaitError(error) {
|
|
1843
|
+
if (error instanceof Error && error.message) {
|
|
1844
|
+
return error.message;
|
|
1845
|
+
}
|
|
1846
|
+
if (error === void 0 || error === null) {
|
|
1847
|
+
return "unknown error";
|
|
1848
|
+
}
|
|
1849
|
+
return String(error);
|
|
1850
|
+
}
|
|
1851
|
+
function anyAbortSignal(signals) {
|
|
1852
|
+
const active = signals.filter((signal) => Boolean(signal));
|
|
1853
|
+
if (active.length === 0) {
|
|
1854
|
+
return void 0;
|
|
1855
|
+
}
|
|
1856
|
+
if (active.length === 1) {
|
|
1857
|
+
return active[0];
|
|
1858
|
+
}
|
|
1859
|
+
const controller = new AbortController();
|
|
1860
|
+
const onAbort = (event) => {
|
|
1861
|
+
cleanup();
|
|
1862
|
+
const signal = event.target;
|
|
1863
|
+
controller.abort(signal.reason ?? createAbortError());
|
|
1864
|
+
};
|
|
1865
|
+
const cleanup = () => {
|
|
1866
|
+
for (const signal of active) {
|
|
1867
|
+
signal.removeEventListener("abort", onAbort);
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
for (const signal of active) {
|
|
1871
|
+
if (signal.aborted) {
|
|
1872
|
+
controller.abort(signal.reason ?? createAbortError());
|
|
1873
|
+
return controller.signal;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
for (const signal of active) {
|
|
1877
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1878
|
+
}
|
|
1879
|
+
return controller.signal;
|
|
1880
|
+
}
|
|
1881
|
+
function throwIfAborted(signal) {
|
|
1882
|
+
if (!signal?.aborted) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
throw signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason);
|
|
1886
|
+
}
|
|
1887
|
+
async function waitForAbortable(promise, signal) {
|
|
1888
|
+
if (!signal) {
|
|
1889
|
+
return promise;
|
|
1890
|
+
}
|
|
1891
|
+
throwIfAborted(signal);
|
|
1892
|
+
return new Promise((resolve, reject) => {
|
|
1893
|
+
const onAbort = () => {
|
|
1894
|
+
cleanup();
|
|
1895
|
+
reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
|
|
1896
|
+
};
|
|
1897
|
+
const cleanup = () => {
|
|
1898
|
+
signal.removeEventListener("abort", onAbort);
|
|
1899
|
+
};
|
|
1900
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1901
|
+
promise.then(
|
|
1902
|
+
(value) => {
|
|
1903
|
+
cleanup();
|
|
1904
|
+
resolve(value);
|
|
1905
|
+
},
|
|
1906
|
+
(error) => {
|
|
1907
|
+
cleanup();
|
|
1908
|
+
reject(error);
|
|
1909
|
+
}
|
|
1910
|
+
);
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
async function consumeProcessLogSse(body, listener, signal) {
|
|
1914
|
+
const reader = body.getReader();
|
|
1915
|
+
const decoder = new TextDecoder();
|
|
1916
|
+
let buffer = "";
|
|
1917
|
+
try {
|
|
1918
|
+
while (!signal.aborted) {
|
|
1919
|
+
const { done, value } = await reader.read();
|
|
1920
|
+
if (done) {
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
1924
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
1925
|
+
while (separatorIndex !== -1) {
|
|
1926
|
+
const chunk = buffer.slice(0, separatorIndex);
|
|
1927
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
1928
|
+
const entry = parseProcessLogSseChunk(chunk);
|
|
1929
|
+
if (entry) {
|
|
1930
|
+
listener(entry);
|
|
1931
|
+
}
|
|
1932
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
if (signal.aborted || isAbortError(error)) {
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
throw error;
|
|
1940
|
+
} finally {
|
|
1941
|
+
reader.releaseLock();
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function parseProcessLogSseChunk(chunk) {
|
|
1945
|
+
if (!chunk.trim()) {
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
let eventName = "message";
|
|
1949
|
+
const dataLines = [];
|
|
1950
|
+
for (const line of chunk.split("\n")) {
|
|
1951
|
+
if (!line || line.startsWith(":")) {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (line.startsWith("event:")) {
|
|
1955
|
+
eventName = line.slice(6).trim();
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
if (line.startsWith("data:")) {
|
|
1959
|
+
dataLines.push(line.slice(5).trimStart());
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (eventName !== "log") {
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
const data = dataLines.join("\n");
|
|
1966
|
+
if (!data.trim()) {
|
|
1967
|
+
return null;
|
|
1968
|
+
}
|
|
1969
|
+
return JSON.parse(data);
|
|
1970
|
+
}
|
|
1971
|
+
function toWebSocketUrl(url) {
|
|
1972
|
+
const parsed = new URL(url);
|
|
1973
|
+
if (parsed.protocol === "http:") {
|
|
1974
|
+
parsed.protocol = "ws:";
|
|
1975
|
+
} else if (parsed.protocol === "https:") {
|
|
1976
|
+
parsed.protocol = "wss:";
|
|
1977
|
+
}
|
|
1978
|
+
return parsed.toString();
|
|
1979
|
+
}
|
|
1980
|
+
function isAbortError(error) {
|
|
1981
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1982
|
+
}
|
|
1983
|
+
function createAbortError(reason) {
|
|
1984
|
+
if (reason instanceof Error) {
|
|
1985
|
+
return reason;
|
|
1986
|
+
}
|
|
1987
|
+
const message = typeof reason === "string" ? reason : "This operation was aborted.";
|
|
1988
|
+
if (typeof DOMException !== "undefined") {
|
|
1989
|
+
return new DOMException(message, "AbortError");
|
|
1990
|
+
}
|
|
1991
|
+
const error = new Error(message);
|
|
1992
|
+
error.name = "AbortError";
|
|
1993
|
+
return error;
|
|
1994
|
+
}
|
|
1995
|
+
function sleep(ms, signal) {
|
|
1996
|
+
if (!signal) {
|
|
1997
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1998
|
+
}
|
|
1999
|
+
throwIfAborted(signal);
|
|
2000
|
+
return new Promise((resolve, reject) => {
|
|
2001
|
+
const timer = setTimeout(() => {
|
|
2002
|
+
cleanup();
|
|
2003
|
+
resolve();
|
|
2004
|
+
}, ms);
|
|
2005
|
+
const onAbort = () => {
|
|
2006
|
+
cleanup();
|
|
2007
|
+
reject(signal.reason instanceof Error ? signal.reason : createAbortError(signal.reason));
|
|
2008
|
+
};
|
|
2009
|
+
const cleanup = () => {
|
|
2010
|
+
clearTimeout(timer);
|
|
2011
|
+
signal.removeEventListener("abort", onAbort);
|
|
2012
|
+
};
|
|
2013
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
999
2016
|
|
|
1000
2017
|
// src/index.ts
|
|
1001
|
-
import { AcpRpcError } from "acp-http-client";
|
|
2018
|
+
import { AcpRpcError as AcpRpcError2 } from "acp-http-client";
|
|
1002
2019
|
|
|
1003
2020
|
// src/inspector.ts
|
|
1004
2021
|
function buildInspectorUrl(options) {
|
|
@@ -1014,12 +2031,16 @@ function buildInspectorUrl(options) {
|
|
|
1014
2031
|
return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
|
|
1015
2032
|
}
|
|
1016
2033
|
export {
|
|
1017
|
-
AcpRpcError,
|
|
2034
|
+
AcpRpcError2 as AcpRpcError,
|
|
1018
2035
|
InMemorySessionPersistDriver,
|
|
1019
2036
|
LiveAcpConnection,
|
|
2037
|
+
ProcessTerminalSession,
|
|
1020
2038
|
SandboxAgent,
|
|
1021
2039
|
SandboxAgentError,
|
|
1022
2040
|
Session,
|
|
2041
|
+
UnsupportedSessionCategoryError,
|
|
2042
|
+
UnsupportedSessionConfigOptionError,
|
|
2043
|
+
UnsupportedSessionValueError,
|
|
1023
2044
|
buildInspectorUrl
|
|
1024
2045
|
};
|
|
1025
2046
|
//# sourceMappingURL=index.js.map
|