sandbox-agent 0.2.1 → 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 +741 -9
- package/dist/index.js +827 -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
|
}
|
|
@@ -382,10 +463,15 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
382
463
|
token;
|
|
383
464
|
fetcher;
|
|
384
465
|
defaultHeaders;
|
|
466
|
+
healthWait;
|
|
467
|
+
healthWaitAbortController = new AbortController();
|
|
385
468
|
persist;
|
|
386
469
|
replayMaxEvents;
|
|
387
470
|
replayMaxChars;
|
|
388
471
|
spawnHandle;
|
|
472
|
+
healthPromise;
|
|
473
|
+
healthError;
|
|
474
|
+
disposed = false;
|
|
389
475
|
liveConnections = /* @__PURE__ */ new Map();
|
|
390
476
|
pendingLiveConnections = /* @__PURE__ */ new Map();
|
|
391
477
|
sessionHandles = /* @__PURE__ */ new Map();
|
|
@@ -405,9 +491,11 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
405
491
|
}
|
|
406
492
|
this.fetcher = resolvedFetch;
|
|
407
493
|
this.defaultHeaders = options.headers;
|
|
494
|
+
this.healthWait = normalizeHealthWaitOptions(options.waitForHealth, options.signal);
|
|
408
495
|
this.persist = options.persist ?? new InMemorySessionPersistDriver();
|
|
409
496
|
this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
|
|
410
497
|
this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
|
|
498
|
+
this.startHealthWait();
|
|
411
499
|
}
|
|
412
500
|
static async connect(options) {
|
|
413
501
|
return new _SandboxAgent(options);
|
|
@@ -425,6 +513,7 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
425
513
|
token: handle.token,
|
|
426
514
|
fetch: options.fetch,
|
|
427
515
|
headers: options.headers,
|
|
516
|
+
waitForHealth: false,
|
|
428
517
|
persist: options.persist,
|
|
429
518
|
replayMaxEvents: options.replayMaxEvents,
|
|
430
519
|
replayMaxChars: options.replayMaxChars
|
|
@@ -433,6 +522,8 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
433
522
|
return client;
|
|
434
523
|
}
|
|
435
524
|
async dispose() {
|
|
525
|
+
this.disposed = true;
|
|
526
|
+
this.healthWaitAbortController.abort(createAbortError("SandboxAgent was disposed."));
|
|
436
527
|
const connections = [...this.liveConnections.values()];
|
|
437
528
|
this.liveConnections.clear();
|
|
438
529
|
const pending = [...this.pendingLiveConnections.values()];
|
|
@@ -484,12 +575,32 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
484
575
|
agentSessionId: response.sessionId,
|
|
485
576
|
lastConnectionId: live.connectionId,
|
|
486
577
|
createdAt: nowMs(),
|
|
487
|
-
sessionInit
|
|
578
|
+
sessionInit,
|
|
579
|
+
configOptions: cloneConfigOptions(response.configOptions),
|
|
580
|
+
modes: cloneModes(response.modes)
|
|
488
581
|
};
|
|
489
582
|
await this.persist.updateSession(record);
|
|
490
583
|
this.nextSessionEventIndexBySession.set(record.id, 1);
|
|
491
584
|
live.bindSession(record.id, record.agentSessionId);
|
|
492
|
-
|
|
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;
|
|
493
604
|
}
|
|
494
605
|
async resumeSession(id) {
|
|
495
606
|
const existing = await this.persist.getSession(id);
|
|
@@ -507,7 +618,9 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
507
618
|
...existing,
|
|
508
619
|
agentSessionId: recreated.sessionId,
|
|
509
620
|
lastConnectionId: live.connectionId,
|
|
510
|
-
destroyedAt: void 0
|
|
621
|
+
destroyedAt: void 0,
|
|
622
|
+
configOptions: cloneConfigOptions(recreated.configOptions),
|
|
623
|
+
modes: cloneModes(recreated.modes)
|
|
511
624
|
};
|
|
512
625
|
await this.persist.updateSession(updated);
|
|
513
626
|
live.bindSession(updated.id, updated.agentSessionId);
|
|
@@ -517,15 +630,26 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
517
630
|
async resumeOrCreateSession(request) {
|
|
518
631
|
const existing = await this.persist.getSession(request.id);
|
|
519
632
|
if (existing) {
|
|
520
|
-
|
|
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;
|
|
521
644
|
}
|
|
522
645
|
return this.createSession(request);
|
|
523
646
|
}
|
|
524
647
|
async destroySession(id) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
648
|
+
try {
|
|
649
|
+
await this.sendSessionMethodInternal(id, SESSION_CANCEL_METHOD, {}, {}, true);
|
|
650
|
+
} catch {
|
|
528
651
|
}
|
|
652
|
+
const existing = await this.requireSessionRecord(id);
|
|
529
653
|
const updated = {
|
|
530
654
|
...existing,
|
|
531
655
|
destroyedAt: nowMs()
|
|
@@ -533,7 +657,132 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
533
657
|
await this.persist.updateSession(updated);
|
|
534
658
|
return this.upsertSessionHandle(updated);
|
|
535
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
|
+
}
|
|
536
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
|
+
}
|
|
537
786
|
const record = await this.persist.getSession(sessionId);
|
|
538
787
|
if (!record) {
|
|
539
788
|
throw new Error(`session '${sessionId}' not found`);
|
|
@@ -541,15 +790,73 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
541
790
|
const live = await this.getLiveConnection(record.agent);
|
|
542
791
|
if (!live.hasBoundSession(record.id, record.agentSessionId)) {
|
|
543
792
|
const restored = await this.resumeSession(record.id);
|
|
544
|
-
return this.
|
|
793
|
+
return this.sendSessionMethodInternal(restored.id, method, params, options, allowManagedCancel);
|
|
545
794
|
}
|
|
546
795
|
const response = await live.sendSessionMethod(record.id, method, params, options);
|
|
796
|
+
await this.persistSessionStateFromMethod(record.id, method, params, response);
|
|
547
797
|
const refreshed = await this.requireSessionRecord(record.id);
|
|
548
798
|
return {
|
|
549
799
|
session: this.upsertSessionHandle(refreshed),
|
|
550
800
|
response
|
|
551
801
|
};
|
|
552
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
|
+
}
|
|
553
860
|
onSessionEvent(sessionId, listener) {
|
|
554
861
|
const listeners = this.eventListeners.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
555
862
|
listeners.add(listener);
|
|
@@ -566,16 +873,16 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
566
873
|
};
|
|
567
874
|
}
|
|
568
875
|
async getHealth() {
|
|
569
|
-
return this.
|
|
876
|
+
return this.requestHealth();
|
|
570
877
|
}
|
|
571
878
|
async listAgents(options) {
|
|
572
879
|
return this.requestJson("GET", `${API_PREFIX}/agents`, {
|
|
573
|
-
query: options
|
|
880
|
+
query: toAgentQuery(options)
|
|
574
881
|
});
|
|
575
882
|
}
|
|
576
883
|
async getAgent(agent, options) {
|
|
577
884
|
return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}`, {
|
|
578
|
-
query: options
|
|
885
|
+
query: toAgentQuery(options)
|
|
579
886
|
});
|
|
580
887
|
}
|
|
581
888
|
async installAgent(agent, request = {}) {
|
|
@@ -647,7 +954,104 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
647
954
|
async deleteSkillsConfig(query) {
|
|
648
955
|
await this.requestRaw("DELETE", `${API_PREFIX}/config/skills`, { query });
|
|
649
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
|
+
}
|
|
650
1053
|
async getLiveConnection(agent) {
|
|
1054
|
+
await this.awaitHealthy();
|
|
651
1055
|
const existing = this.liveConnections.get(agent);
|
|
652
1056
|
if (existing) {
|
|
653
1057
|
return existing;
|
|
@@ -700,6 +1104,7 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
700
1104
|
payload: cloneEnvelope(envelope)
|
|
701
1105
|
};
|
|
702
1106
|
await this.persist.insertEvent(event);
|
|
1107
|
+
await this.persistSessionStateFromEvent(localSessionId, envelope, direction);
|
|
703
1108
|
const listeners = this.eventListeners.get(localSessionId);
|
|
704
1109
|
if (!listeners || listeners.size === 0) {
|
|
705
1110
|
return;
|
|
@@ -708,6 +1113,46 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
708
1113
|
listener(event);
|
|
709
1114
|
}
|
|
710
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
|
+
}
|
|
711
1156
|
async allocateSessionEventIndex(sessionId) {
|
|
712
1157
|
await this.ensureSessionEventIndexSeeded(sessionId);
|
|
713
1158
|
const nextIndex = this.nextSessionEventIndexBySession.get(sessionId) ?? 1;
|
|
@@ -793,7 +1238,8 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
793
1238
|
body: options.body,
|
|
794
1239
|
headers: options.headers,
|
|
795
1240
|
accept: options.accept ?? "application/json",
|
|
796
|
-
signal: options.signal
|
|
1241
|
+
signal: options.signal,
|
|
1242
|
+
skipReadyWait: options.skipReadyWait
|
|
797
1243
|
});
|
|
798
1244
|
if (response.status === 204) {
|
|
799
1245
|
return void 0;
|
|
@@ -801,6 +1247,9 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
801
1247
|
return await response.json();
|
|
802
1248
|
}
|
|
803
1249
|
async requestRaw(method, path, options = {}) {
|
|
1250
|
+
if (!options.skipReadyWait) {
|
|
1251
|
+
await this.awaitHealthy(options.signal);
|
|
1252
|
+
}
|
|
804
1253
|
const url = this.buildUrl(path, options.query);
|
|
805
1254
|
const headers = this.buildHeaders(options.headers);
|
|
806
1255
|
if (options.accept) {
|
|
@@ -830,6 +1279,64 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
830
1279
|
}
|
|
831
1280
|
return response;
|
|
832
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
|
+
}
|
|
833
1340
|
buildHeaders(extra) {
|
|
834
1341
|
const headers = new Headers(this.defaultHeaders ?? void 0);
|
|
835
1342
|
if (this.token) {
|
|
@@ -853,6 +1360,12 @@ var SandboxAgent = class _SandboxAgent {
|
|
|
853
1360
|
}
|
|
854
1361
|
return url.toString();
|
|
855
1362
|
}
|
|
1363
|
+
async requestHealth(options = {}) {
|
|
1364
|
+
return this.requestJson("GET", `${API_PREFIX}/health`, {
|
|
1365
|
+
signal: options.signal,
|
|
1366
|
+
skipReadyWait: true
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
856
1369
|
};
|
|
857
1370
|
async function autoAuthenticate(acp, methods) {
|
|
858
1371
|
const envBased = methods.find(
|
|
@@ -866,6 +1379,15 @@ async function autoAuthenticate(acp, methods) {
|
|
|
866
1379
|
} catch {
|
|
867
1380
|
}
|
|
868
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
|
+
}
|
|
869
1391
|
function normalizeSessionInit(value) {
|
|
870
1392
|
if (!value) {
|
|
871
1393
|
return {
|
|
@@ -973,6 +1495,20 @@ function normalizePositiveInt(value, fallback) {
|
|
|
973
1495
|
}
|
|
974
1496
|
return Math.floor(value);
|
|
975
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
|
+
}
|
|
976
1512
|
function normalizeSpawnOptions(spawn, defaultEnabled) {
|
|
977
1513
|
if (spawn === false) {
|
|
978
1514
|
return { enabled: false };
|
|
@@ -996,9 +1532,282 @@ async function readProblem(response) {
|
|
|
996
1532
|
return void 0;
|
|
997
1533
|
}
|
|
998
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
|
+
}
|
|
999
1808
|
|
|
1000
1809
|
// src/index.ts
|
|
1001
|
-
import { AcpRpcError } from "acp-http-client";
|
|
1810
|
+
import { AcpRpcError as AcpRpcError2 } from "acp-http-client";
|
|
1002
1811
|
|
|
1003
1812
|
// src/inspector.ts
|
|
1004
1813
|
function buildInspectorUrl(options) {
|
|
@@ -1014,12 +1823,15 @@ function buildInspectorUrl(options) {
|
|
|
1014
1823
|
return `${normalized}/ui/${queryString ? `?${queryString}` : ""}`;
|
|
1015
1824
|
}
|
|
1016
1825
|
export {
|
|
1017
|
-
AcpRpcError,
|
|
1826
|
+
AcpRpcError2 as AcpRpcError,
|
|
1018
1827
|
InMemorySessionPersistDriver,
|
|
1019
1828
|
LiveAcpConnection,
|
|
1020
1829
|
SandboxAgent,
|
|
1021
1830
|
SandboxAgentError,
|
|
1022
1831
|
Session,
|
|
1832
|
+
UnsupportedSessionCategoryError,
|
|
1833
|
+
UnsupportedSessionConfigOptionError,
|
|
1834
|
+
UnsupportedSessionValueError,
|
|
1023
1835
|
buildInspectorUrl
|
|
1024
1836
|
};
|
|
1025
1837
|
//# sourceMappingURL=index.js.map
|