@letta-ai/letta-agent-sdk 0.3.1 → 0.3.3
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/AGENTS.md +47 -0
- package/README.md +17 -0
- package/dist/app-server-management.d.ts +58 -0
- package/dist/app-server-management.d.ts.map +1 -1
- package/dist/app-server-session.d.ts +5 -0
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-base.d.ts +7 -0
- package/dist/client-base.d.ts.map +1 -1
- package/dist/client-entry.js +1123 -776
- package/dist/client-entry.js.map +13 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/cloud-sandbox.d.ts +33 -0
- package/dist/cloud-sandbox.d.ts.map +1 -0
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1176 -791
- package/dist/index.js.map +17 -13
- package/dist/local-app-server-session.d.ts +1 -1
- package/dist/local-app-server-session.d.ts.map +1 -1
- package/dist/management.d.ts +7 -0
- package/dist/management.d.ts.map +1 -1
- package/dist/remote-client-session-core.d.ts +24 -93
- package/dist/remote-client-session-core.d.ts.map +1 -1
- package/dist/remote-session-protocol.d.ts +130 -0
- package/dist/remote-session-protocol.d.ts.map +1 -0
- package/dist/remote-turn-coordinator.d.ts +49 -0
- package/dist/remote-turn-coordinator.d.ts.map +1 -0
- package/dist/request-ids.d.ts +24 -0
- package/dist/request-ids.d.ts.map +1 -0
- package/dist/session.d.ts +10 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/types.d.ts +6 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/app-server-management.ts +641 -0
- package/src/app-server-session.ts +948 -0
- package/src/cli-resolver.ts +46 -0
- package/src/client-base.ts +482 -0
- package/src/client-entry.ts +31 -0
- package/src/client.ts +138 -0
- package/src/cloud-management.ts +360 -0
- package/src/cloud-sandbox.ts +117 -0
- package/src/cloud-session.ts +1313 -0
- package/src/index.ts +440 -0
- package/src/interactiveToolPolicy.ts +62 -0
- package/src/local-app-server-session.ts +39 -0
- package/src/local-app-server.ts +137 -0
- package/src/management-types.ts +133 -0
- package/src/management.ts +206 -0
- package/src/protocol.ts +249 -0
- package/src/remote-client-session-core.ts +786 -0
- package/src/remote-session-protocol.ts +660 -0
- package/src/remote-turn-coordinator.ts +505 -0
- package/src/remote.ts +177 -0
- package/src/repositories.ts +340 -0
- package/src/request-ids.ts +33 -0
- package/src/session.ts +1638 -0
- package/src/stream-events.ts +88 -0
- package/src/tool-helpers.ts +147 -0
- package/src/transport.ts +484 -0
- package/src/types.ts +1328 -0
- package/src/validation.ts +223 -0
- package/src/websocket.ts +22 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Generated bundle. Canonical modules are published under src/ and maintained in the letta-agent-sdk repository.
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -701,7 +702,56 @@ function createAppServerClient(options) {
|
|
|
701
702
|
return new AppServerClient(options);
|
|
702
703
|
}
|
|
703
704
|
|
|
705
|
+
// src/request-ids.ts
|
|
706
|
+
var processRequestCounter = 0;
|
|
707
|
+
function createRequestIdGenerator() {
|
|
708
|
+
const nonce = Math.random().toString(36).slice(2, 10);
|
|
709
|
+
return (prefix = "req") => `${prefix}-${nonce}-${++processRequestCounter}`;
|
|
710
|
+
}
|
|
711
|
+
function applyUniqueRequestIds(client) {
|
|
712
|
+
client.nextRequestId = createRequestIdGenerator();
|
|
713
|
+
return client;
|
|
714
|
+
}
|
|
715
|
+
|
|
704
716
|
// src/app-server-management.ts
|
|
717
|
+
var DEFAULT_IDLE_LINGER_MS = 250;
|
|
718
|
+
var registeredTransportsByUrl = new Map;
|
|
719
|
+
function appServerUrlKey(value) {
|
|
720
|
+
try {
|
|
721
|
+
const url = new URL(value);
|
|
722
|
+
if (url.protocol === "http:")
|
|
723
|
+
url.protocol = "ws:";
|
|
724
|
+
if (url.protocol === "https:")
|
|
725
|
+
url.protocol = "wss:";
|
|
726
|
+
url.hash = "";
|
|
727
|
+
url.searchParams.delete("channel");
|
|
728
|
+
return url.toString();
|
|
729
|
+
} catch {
|
|
730
|
+
return value;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function registerManagementTransport(url, transport) {
|
|
734
|
+
const key = appServerUrlKey(url);
|
|
735
|
+
const registration = { transport };
|
|
736
|
+
const transports = registeredTransportsByUrl.get(key) ?? new Set;
|
|
737
|
+
transports.add(registration);
|
|
738
|
+
registeredTransportsByUrl.set(key, transports);
|
|
739
|
+
return () => {
|
|
740
|
+
transports.delete(registration);
|
|
741
|
+
if (transports.size === 0) {
|
|
742
|
+
registeredTransportsByUrl.delete(key);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
async function releaseAppServerManagementConnections(url) {
|
|
747
|
+
const key = appServerUrlKey(url);
|
|
748
|
+
while (true) {
|
|
749
|
+
const transports = [...registeredTransportsByUrl.get(key) ?? []];
|
|
750
|
+
if (transports.length === 0)
|
|
751
|
+
return;
|
|
752
|
+
await Promise.all(transports.map(({ transport }) => transport.releaseIdleConnection()));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
705
755
|
function ensureResponse(response, value, fallback) {
|
|
706
756
|
if (!response.success || value == null) {
|
|
707
757
|
throw new Error(response.error ?? fallback);
|
|
@@ -737,8 +787,16 @@ function stringRecord(value) {
|
|
|
737
787
|
|
|
738
788
|
class AppServerManagementTransport {
|
|
739
789
|
options;
|
|
790
|
+
connectionPromise = null;
|
|
791
|
+
releasePromise = null;
|
|
792
|
+
closingConnections = new Set;
|
|
793
|
+
inFlightRequests = 0;
|
|
794
|
+
idleWaiters = new Set;
|
|
795
|
+
idleTimer = null;
|
|
796
|
+
idleLingerMs;
|
|
740
797
|
constructor(options) {
|
|
741
798
|
this.options = options;
|
|
799
|
+
this.idleLingerMs = options.idleLingerMs ?? DEFAULT_IDLE_LINGER_MS;
|
|
742
800
|
}
|
|
743
801
|
async listAgents(query) {
|
|
744
802
|
const response = await this.request("agent_list", { query }, "agent_list_response");
|
|
@@ -805,38 +863,206 @@ class AppServerManagementTransport {
|
|
|
805
863
|
}
|
|
806
864
|
return { messages: response.messages };
|
|
807
865
|
}
|
|
866
|
+
releaseIdleConnection() {
|
|
867
|
+
this.clearIdleTimer();
|
|
868
|
+
if (this.releasePromise)
|
|
869
|
+
return this.releasePromise;
|
|
870
|
+
const release = this.releaseConnectionWhenIdle();
|
|
871
|
+
this.releasePromise = release;
|
|
872
|
+
release.then(() => {
|
|
873
|
+
if (this.releasePromise === release) {
|
|
874
|
+
this.releasePromise = null;
|
|
875
|
+
}
|
|
876
|
+
}, () => {
|
|
877
|
+
if (this.releasePromise === release) {
|
|
878
|
+
this.releasePromise = null;
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
return release;
|
|
882
|
+
}
|
|
808
883
|
async request(type, body, responseType) {
|
|
809
|
-
|
|
884
|
+
if (this.releasePromise) {
|
|
885
|
+
await this.releasePromise;
|
|
886
|
+
}
|
|
887
|
+
if (this.closingConnections.size > 0) {
|
|
888
|
+
await Promise.all([...this.closingConnections]);
|
|
889
|
+
}
|
|
890
|
+
this.clearIdleTimer();
|
|
891
|
+
this.inFlightRequests += 1;
|
|
892
|
+
try {
|
|
893
|
+
const { client } = await this.acquireConnection();
|
|
894
|
+
const command = {
|
|
895
|
+
type,
|
|
896
|
+
request_id: client.nextRequestId(type),
|
|
897
|
+
...body
|
|
898
|
+
};
|
|
899
|
+
const response = await client.request(command, {
|
|
900
|
+
predicate: (message) => message.type === responseType
|
|
901
|
+
});
|
|
902
|
+
return response;
|
|
903
|
+
} finally {
|
|
904
|
+
this.inFlightRequests -= 1;
|
|
905
|
+
if (this.inFlightRequests === 0) {
|
|
906
|
+
const waiters = [...this.idleWaiters];
|
|
907
|
+
this.idleWaiters.clear();
|
|
908
|
+
for (const resolve of waiters)
|
|
909
|
+
resolve();
|
|
910
|
+
if (!this.releasePromise) {
|
|
911
|
+
this.scheduleIdleRelease();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
async releaseConnectionWhenIdle() {
|
|
917
|
+
if (this.inFlightRequests > 0) {
|
|
918
|
+
await new Promise((resolve) => {
|
|
919
|
+
this.idleWaiters.add(resolve);
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
this.clearIdleTimer();
|
|
923
|
+
const promise = this.connectionPromise;
|
|
924
|
+
try {
|
|
925
|
+
if (promise) {
|
|
926
|
+
this.connectionPromise = null;
|
|
927
|
+
await this.trackClosingConnection(await promise);
|
|
928
|
+
}
|
|
929
|
+
if (this.closingConnections.size > 0) {
|
|
930
|
+
await Promise.all([...this.closingConnections]);
|
|
931
|
+
}
|
|
932
|
+
} catch {}
|
|
933
|
+
}
|
|
934
|
+
acquireConnection() {
|
|
935
|
+
if (this.connectionPromise)
|
|
936
|
+
return this.connectionPromise;
|
|
937
|
+
const promise = this.openConnection().then((connection) => {
|
|
938
|
+
connection.detachDisconnect = connection.client.onDisconnect(() => {
|
|
939
|
+
this.discardConnection(promise, connection);
|
|
940
|
+
});
|
|
941
|
+
return connection;
|
|
942
|
+
}, (error) => {
|
|
943
|
+
if (this.connectionPromise === promise) {
|
|
944
|
+
this.connectionPromise = null;
|
|
945
|
+
}
|
|
946
|
+
throw error;
|
|
947
|
+
});
|
|
948
|
+
this.connectionPromise = promise;
|
|
949
|
+
return promise;
|
|
950
|
+
}
|
|
951
|
+
async openConnection() {
|
|
952
|
+
const ownedConnection = this.options.url ? null : await this.options.connect?.() ?? null;
|
|
810
953
|
const url = this.options.url ?? ownedConnection?.url;
|
|
811
954
|
if (!url) {
|
|
812
955
|
throw new Error("App-server management requires a url or connect hook.");
|
|
813
956
|
}
|
|
957
|
+
const unregister = registerManagementTransport(url, this);
|
|
814
958
|
let client = null;
|
|
815
959
|
try {
|
|
816
|
-
client = createAppServerClient({
|
|
960
|
+
client = applyUniqueRequestIds(createAppServerClient({
|
|
817
961
|
url,
|
|
818
962
|
...this.options.authToken !== undefined ? { authToken: this.options.authToken } : {},
|
|
819
963
|
...this.options.WebSocket ? {
|
|
820
964
|
WebSocket: this.options.WebSocket
|
|
821
965
|
} : {},
|
|
822
966
|
...this.options.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.options.requestTimeoutMs } : {}
|
|
823
|
-
});
|
|
967
|
+
}));
|
|
824
968
|
await client.connect();
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
969
|
+
} catch (error) {
|
|
970
|
+
try {
|
|
971
|
+
if (client) {
|
|
972
|
+
const controlClosed = waitForSocketClose(client.control);
|
|
973
|
+
client.close();
|
|
974
|
+
ownedConnection?.close();
|
|
975
|
+
await controlClosed;
|
|
976
|
+
} else {
|
|
977
|
+
ownedConnection?.close();
|
|
978
|
+
}
|
|
979
|
+
} catch {} finally {
|
|
980
|
+
unregister();
|
|
981
|
+
}
|
|
982
|
+
throw error;
|
|
837
983
|
}
|
|
984
|
+
return {
|
|
985
|
+
client,
|
|
986
|
+
ownedConnection,
|
|
987
|
+
detachDisconnect: () => {},
|
|
988
|
+
unregister
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
discardConnection(promise, connection) {
|
|
992
|
+
if (this.connectionPromise === promise) {
|
|
993
|
+
this.connectionPromise = null;
|
|
994
|
+
this.clearIdleTimer();
|
|
995
|
+
}
|
|
996
|
+
this.trackClosingConnection(connection);
|
|
997
|
+
}
|
|
998
|
+
trackClosingConnection(connection) {
|
|
999
|
+
const closing = closeConnection(connection);
|
|
1000
|
+
this.closingConnections.add(closing);
|
|
1001
|
+
closing.then(() => this.closingConnections.delete(closing), () => this.closingConnections.delete(closing));
|
|
1002
|
+
return closing;
|
|
1003
|
+
}
|
|
1004
|
+
scheduleIdleRelease() {
|
|
1005
|
+
if (!this.connectionPromise)
|
|
1006
|
+
return;
|
|
1007
|
+
this.clearIdleTimer();
|
|
1008
|
+
const timer = setTimeout(() => {
|
|
1009
|
+
this.idleTimer = null;
|
|
1010
|
+
this.releaseIdleConnection();
|
|
1011
|
+
}, this.idleLingerMs);
|
|
1012
|
+
this.idleTimer = timer;
|
|
1013
|
+
timer.unref?.();
|
|
1014
|
+
}
|
|
1015
|
+
clearIdleTimer() {
|
|
1016
|
+
if (this.idleTimer === null)
|
|
1017
|
+
return;
|
|
1018
|
+
clearTimeout(this.idleTimer);
|
|
1019
|
+
this.idleTimer = null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async function closeConnection(connection) {
|
|
1023
|
+
connection.detachDisconnect();
|
|
1024
|
+
const controlClosed = waitForSocketClose(connection.client.control);
|
|
1025
|
+
try {
|
|
1026
|
+
connection.client.close();
|
|
1027
|
+
connection.ownedConnection?.close();
|
|
1028
|
+
await controlClosed;
|
|
1029
|
+
} finally {
|
|
1030
|
+
connection.unregister();
|
|
838
1031
|
}
|
|
839
1032
|
}
|
|
1033
|
+
function waitForSocketClose(socket) {
|
|
1034
|
+
if (socket.readyState === 3)
|
|
1035
|
+
return Promise.resolve();
|
|
1036
|
+
return new Promise((resolve) => {
|
|
1037
|
+
let settled = false;
|
|
1038
|
+
let detach = () => {};
|
|
1039
|
+
const finish = () => {
|
|
1040
|
+
if (settled)
|
|
1041
|
+
return;
|
|
1042
|
+
settled = true;
|
|
1043
|
+
clearTimeout(timeout);
|
|
1044
|
+
detach();
|
|
1045
|
+
resolve();
|
|
1046
|
+
};
|
|
1047
|
+
if (socket.addEventListener) {
|
|
1048
|
+
socket.addEventListener("close", finish);
|
|
1049
|
+
detach = () => socket.removeEventListener?.("close", finish);
|
|
1050
|
+
} else if (socket.once) {
|
|
1051
|
+
socket.once("close", finish);
|
|
1052
|
+
detach = () => socket.off?.("close", finish);
|
|
1053
|
+
} else if (socket.on) {
|
|
1054
|
+
socket.on("close", finish);
|
|
1055
|
+
detach = () => socket.off?.("close", finish);
|
|
1056
|
+
} else {
|
|
1057
|
+
resolve();
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const timeout = setTimeout(finish, 1000);
|
|
1061
|
+
timeout.unref?.();
|
|
1062
|
+
if (socket.readyState === 3)
|
|
1063
|
+
finish();
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
840
1066
|
|
|
841
1067
|
// node_modules/@letta-ai/letta-code/dist/agent-presets.js
|
|
842
1068
|
var LETTA_CODE_ORIGIN_TAG = "origin:letta-code";
|
|
@@ -5505,7 +5731,7 @@ function buildCanUseToolContext(request, requestId) {
|
|
|
5505
5731
|
return context;
|
|
5506
5732
|
}
|
|
5507
5733
|
|
|
5508
|
-
// src/remote-
|
|
5734
|
+
// src/remote-session-protocol.ts
|
|
5509
5735
|
var FAILURE_STOP_REASONS = new Set([
|
|
5510
5736
|
"error",
|
|
5511
5737
|
"llm_api_error",
|
|
@@ -5900,856 +6126,930 @@ function normalizeSendMessage(message) {
|
|
|
5900
6126
|
return message;
|
|
5901
6127
|
}
|
|
5902
6128
|
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
controller = null;
|
|
5906
|
-
runtime = null;
|
|
5907
|
-
initialized = false;
|
|
5908
|
-
closed = false;
|
|
5909
|
-
activeTurnStartedAt = 0;
|
|
5910
|
-
_agentId = null;
|
|
5911
|
-
_sessionId = null;
|
|
5912
|
-
_conversationId = null;
|
|
5913
|
-
_model = "";
|
|
5914
|
-
_modelSettings = null;
|
|
6129
|
+
// src/remote-turn-coordinator.ts
|
|
6130
|
+
class RemoteTurnCoordinator {
|
|
5915
6131
|
label;
|
|
5916
6132
|
requestTimeoutMs;
|
|
6133
|
+
onDeviceStatus;
|
|
5917
6134
|
streamQueue = [];
|
|
5918
6135
|
streamResolvers = [];
|
|
5919
|
-
removeMessageHandler = null;
|
|
5920
6136
|
activeTurn = null;
|
|
5921
6137
|
pendingTurns = [];
|
|
5922
6138
|
nextTurnId = 0;
|
|
5923
6139
|
messageCounter = 0;
|
|
5924
6140
|
clientMessageCounter = 0;
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
constructor(mode, config) {
|
|
5929
|
-
this.mode = mode;
|
|
6141
|
+
closed = false;
|
|
6142
|
+
_activeTurnStartedAt = 0;
|
|
6143
|
+
constructor(config) {
|
|
5930
6144
|
this.label = config.label;
|
|
5931
6145
|
this.requestTimeoutMs = config.requestTimeoutMs;
|
|
6146
|
+
this.onDeviceStatus = config.onDeviceStatus;
|
|
5932
6147
|
}
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
throw new Error("Session already initialized");
|
|
5936
|
-
}
|
|
5937
|
-
if (this.closed) {
|
|
5938
|
-
throw new Error("Session is closed");
|
|
5939
|
-
}
|
|
5940
|
-
try {
|
|
5941
|
-
const init = await this.initializeRuntimeController();
|
|
5942
|
-
this.controller = init.controller;
|
|
5943
|
-
this.runtime = init.runtime;
|
|
5944
|
-
this._agentId = init.runtime.agent_id;
|
|
5945
|
-
this._conversationId = init.runtime.conversation_id;
|
|
5946
|
-
this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
|
|
5947
|
-
this._modelSettings = init.modelSettings ?? null;
|
|
5948
|
-
this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
|
|
5949
|
-
this.toolNames = init.tools;
|
|
5950
|
-
this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
|
|
5951
|
-
this.initialized = true;
|
|
5952
|
-
await this.afterRuntimeInitialized();
|
|
5953
|
-
await this.applyPostInitializeOptions();
|
|
5954
|
-
const initMessage = {
|
|
5955
|
-
type: "init",
|
|
5956
|
-
agentId: init.runtime.agent_id,
|
|
5957
|
-
sessionId: this._sessionId,
|
|
5958
|
-
conversationId: init.runtime.conversation_id,
|
|
5959
|
-
model: this._model
|
|
5960
|
-
};
|
|
5961
|
-
if (this.toolNames !== undefined)
|
|
5962
|
-
initMessage.tools = this.toolNames;
|
|
5963
|
-
if (init.skillSources !== undefined) {
|
|
5964
|
-
initMessage.skillSources = init.skillSources;
|
|
5965
|
-
}
|
|
5966
|
-
return initMessage;
|
|
5967
|
-
} catch (error) {
|
|
5968
|
-
this.close();
|
|
5969
|
-
throw error;
|
|
5970
|
-
}
|
|
6148
|
+
get activeTurnStartedAt() {
|
|
6149
|
+
return this._activeTurnStartedAt;
|
|
5971
6150
|
}
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
await this.initialize();
|
|
5975
|
-
}
|
|
5976
|
-
if (!this.controller || !this.runtime) {
|
|
5977
|
-
throw new Error("Session is not initialized");
|
|
5978
|
-
}
|
|
5979
|
-
await this.beforeTurn();
|
|
5980
|
-
const turn = this.trackSentTurn(this.runtime);
|
|
5981
|
-
try {
|
|
5982
|
-
this.controller.sendTurnMessage(this.runtime, message, {
|
|
5983
|
-
clientMessageId: turn.clientMessageId
|
|
5984
|
-
});
|
|
5985
|
-
} catch (error) {
|
|
5986
|
-
this.removeTrackedTurn(turn);
|
|
5987
|
-
throw error;
|
|
5988
|
-
}
|
|
6151
|
+
hasInFlightTurn() {
|
|
6152
|
+
return this.activeTurn !== null || this.pendingTurns.length > 0;
|
|
5989
6153
|
}
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
error: "stream_closed",
|
|
6004
|
-
errorCode: "stream_closed",
|
|
6005
|
-
recoverable: false,
|
|
6006
|
-
errorDetail: "Stream ended before terminal result",
|
|
6007
|
-
durationMs: Date.now() - this.activeTurnStartedAt,
|
|
6008
|
-
conversationId: this._conversationId
|
|
6154
|
+
trackSentTurn(runtime) {
|
|
6155
|
+
const turn = {
|
|
6156
|
+
id: ++this.nextTurnId,
|
|
6157
|
+
runtime,
|
|
6158
|
+
clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
|
|
6159
|
+
queuedAt: Date.now(),
|
|
6160
|
+
startedAt: 0,
|
|
6161
|
+
assistantText: "",
|
|
6162
|
+
runIds: new Set,
|
|
6163
|
+
observedTurnEvidence: false,
|
|
6164
|
+
observedRequiresApprovalStop: false,
|
|
6165
|
+
abortRequested: false,
|
|
6166
|
+
timeout: null
|
|
6009
6167
|
};
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
if (!msg)
|
|
6015
|
-
break;
|
|
6016
|
-
yield msg;
|
|
6017
|
-
if (msg.type === "result")
|
|
6018
|
-
break;
|
|
6168
|
+
if (this.activeTurn) {
|
|
6169
|
+
this.pendingTurns.push(turn);
|
|
6170
|
+
} else {
|
|
6171
|
+
this.activateTurn(turn);
|
|
6019
6172
|
}
|
|
6173
|
+
return turn;
|
|
6020
6174
|
}
|
|
6021
|
-
|
|
6022
|
-
if (
|
|
6023
|
-
|
|
6024
|
-
if (
|
|
6175
|
+
removeTrackedTurn(turn) {
|
|
6176
|
+
if (turn.timeout)
|
|
6177
|
+
clearTimeout(turn.timeout);
|
|
6178
|
+
if (this.activeTurn === turn) {
|
|
6179
|
+
this.activeTurn = null;
|
|
6025
6180
|
return;
|
|
6181
|
+
}
|
|
6182
|
+
const index = this.pendingTurns.indexOf(turn);
|
|
6183
|
+
if (index !== -1)
|
|
6184
|
+
this.pendingTurns.splice(index, 1);
|
|
6185
|
+
}
|
|
6186
|
+
markAbortRequested() {
|
|
6026
6187
|
if (this.activeTurn)
|
|
6027
6188
|
this.activeTurn.abortRequested = true;
|
|
6028
|
-
await this.controller.abort(this.runtime);
|
|
6029
6189
|
}
|
|
6030
|
-
|
|
6031
|
-
if (!
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
if (
|
|
6035
|
-
|
|
6190
|
+
handleProtocolMessage(message, runtime) {
|
|
6191
|
+
if (!sameRuntime2(message, runtime))
|
|
6192
|
+
return;
|
|
6193
|
+
const statusRecord = deviceStatusRecord(message);
|
|
6194
|
+
if (statusRecord) {
|
|
6195
|
+
const status = toSessionDeviceStatus(statusRecord);
|
|
6196
|
+
if (status)
|
|
6197
|
+
this.onDeviceStatus(status);
|
|
6198
|
+
return;
|
|
6036
6199
|
}
|
|
6037
|
-
if (
|
|
6038
|
-
|
|
6200
|
+
if (message.type === "update_queue") {
|
|
6201
|
+
const sdkMessage2 = {
|
|
6202
|
+
type: "queue_update",
|
|
6203
|
+
queue: queueItems(message)
|
|
6204
|
+
};
|
|
6205
|
+
this.enqueue(sdkMessage2);
|
|
6206
|
+
return;
|
|
6039
6207
|
}
|
|
6040
|
-
if (
|
|
6041
|
-
|
|
6208
|
+
if (message.type === "update_loop_status") {
|
|
6209
|
+
this.handleLoopStatusMessage(message);
|
|
6210
|
+
return;
|
|
6042
6211
|
}
|
|
6043
|
-
|
|
6044
|
-
|
|
6212
|
+
const delta = streamDeltaRecord(message);
|
|
6213
|
+
if (!delta)
|
|
6045
6214
|
return;
|
|
6215
|
+
const active = this.activateNextTurnFromProtocol();
|
|
6216
|
+
if (active) {
|
|
6217
|
+
active.observedTurnEvidence = true;
|
|
6218
|
+
const runId = streamDeltaRunId2(delta);
|
|
6219
|
+
if (runId)
|
|
6220
|
+
active.runIds.add(runId);
|
|
6046
6221
|
}
|
|
6047
|
-
const
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
});
|
|
6052
|
-
return response;
|
|
6222
|
+
const sdkMessage = this.transformStreamDelta(delta);
|
|
6223
|
+
if (sdkMessage)
|
|
6224
|
+
this.enqueue(sdkMessage);
|
|
6225
|
+
this.handleTurnTerminalDelta(delta, sdkMessage);
|
|
6053
6226
|
}
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
if (
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6227
|
+
nextMessage() {
|
|
6228
|
+
const next = this.streamQueue.shift();
|
|
6229
|
+
if (next)
|
|
6230
|
+
return Promise.resolve(next);
|
|
6231
|
+
if (this.closed)
|
|
6232
|
+
return Promise.resolve(null);
|
|
6233
|
+
return new Promise((resolve) => {
|
|
6234
|
+
this.streamResolvers.push(resolve);
|
|
6235
|
+
});
|
|
6062
6236
|
}
|
|
6063
|
-
|
|
6064
|
-
if (
|
|
6065
|
-
|
|
6237
|
+
close() {
|
|
6238
|
+
if (this.closed)
|
|
6239
|
+
return;
|
|
6240
|
+
this.closed = true;
|
|
6241
|
+
if (this.activeTurn?.timeout)
|
|
6242
|
+
clearTimeout(this.activeTurn.timeout);
|
|
6243
|
+
for (const turn of this.pendingTurns) {
|
|
6244
|
+
if (turn.timeout)
|
|
6245
|
+
clearTimeout(turn.timeout);
|
|
6246
|
+
}
|
|
6247
|
+
this.activeTurn = null;
|
|
6248
|
+
this.pendingTurns.length = 0;
|
|
6249
|
+
this.resolveAll(null);
|
|
6250
|
+
}
|
|
6251
|
+
activateTurn(turn) {
|
|
6252
|
+
this.activeTurn = turn;
|
|
6253
|
+
turn.startedAt = Date.now();
|
|
6254
|
+
this._activeTurnStartedAt = turn.startedAt;
|
|
6255
|
+
if (this.requestTimeoutMs !== undefined) {
|
|
6256
|
+
turn.timeout = setTimeout(() => {
|
|
6257
|
+
this.failTurn(turn, `Timed out waiting for ${this.label} turn`);
|
|
6258
|
+
}, this.requestTimeoutMs);
|
|
6259
|
+
turn.timeout.unref?.();
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
activateNextTurnFromProtocol() {
|
|
6263
|
+
if (this.activeTurn)
|
|
6264
|
+
return this.activeTurn;
|
|
6265
|
+
const next = this.pendingTurns.shift();
|
|
6266
|
+
if (!next)
|
|
6267
|
+
return null;
|
|
6268
|
+
this.activateTurn(next);
|
|
6269
|
+
return next;
|
|
6270
|
+
}
|
|
6271
|
+
failTurn(turn, detail) {
|
|
6272
|
+
if (this.activeTurn !== turn)
|
|
6273
|
+
return;
|
|
6274
|
+
this.enqueue({
|
|
6275
|
+
type: "error",
|
|
6276
|
+
message: detail,
|
|
6277
|
+
errorCode: "error",
|
|
6278
|
+
stopReason: "error",
|
|
6279
|
+
errorDetail: detail,
|
|
6280
|
+
recoverable: false
|
|
6281
|
+
});
|
|
6282
|
+
this.completeActiveTurn({
|
|
6283
|
+
runtime: turn.runtime,
|
|
6284
|
+
stopReason: "error",
|
|
6285
|
+
runIds: [...turn.runIds],
|
|
6286
|
+
success: false,
|
|
6287
|
+
detail,
|
|
6288
|
+
errorCode: "error"
|
|
6289
|
+
});
|
|
6290
|
+
}
|
|
6291
|
+
completeActiveTurn(turn) {
|
|
6292
|
+
const active = this.activeTurn;
|
|
6293
|
+
if (!active)
|
|
6294
|
+
return;
|
|
6295
|
+
if (active.timeout) {
|
|
6296
|
+
clearTimeout(active.timeout);
|
|
6297
|
+
active.timeout = null;
|
|
6298
|
+
}
|
|
6299
|
+
this.enqueue(this.resultFromTurn(turn, active));
|
|
6300
|
+
this.activeTurn = null;
|
|
6301
|
+
}
|
|
6302
|
+
handleLoopStatusMessage(message) {
|
|
6303
|
+
const status = loopStatusValue(message);
|
|
6304
|
+
if (!status)
|
|
6305
|
+
return;
|
|
6306
|
+
const activeRunIds = loopStatusRunIds(message);
|
|
6307
|
+
const sdkMessage = {
|
|
6308
|
+
type: "loop_status",
|
|
6309
|
+
status,
|
|
6310
|
+
activeRunIds
|
|
6311
|
+
};
|
|
6312
|
+
this.enqueue(sdkMessage);
|
|
6313
|
+
const active = this.activeTurn;
|
|
6314
|
+
if (!active)
|
|
6315
|
+
return;
|
|
6316
|
+
for (const runId of activeRunIds)
|
|
6317
|
+
active.runIds.add(runId);
|
|
6318
|
+
const hadTurnEvidence = active.observedTurnEvidence || active.observedRequiresApprovalStop;
|
|
6319
|
+
if (!hadTurnEvidence)
|
|
6320
|
+
return;
|
|
6321
|
+
if (status === "WAITING_ON_APPROVAL") {
|
|
6322
|
+
this.completeActiveTurn({
|
|
6323
|
+
runtime: active.runtime,
|
|
6324
|
+
stopReason: "requires_approval",
|
|
6325
|
+
runIds: [...active.runIds]
|
|
6326
|
+
});
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6329
|
+
if (status === "WAITING_ON_INPUT" && active.abortRequested) {
|
|
6330
|
+
this.completeActiveTurn({
|
|
6331
|
+
runtime: active.runtime,
|
|
6332
|
+
stopReason: "interrupted",
|
|
6333
|
+
runIds: [...active.runIds],
|
|
6334
|
+
success: false,
|
|
6335
|
+
detail: "Interrupted",
|
|
6336
|
+
errorCode: "interrupted"
|
|
6337
|
+
});
|
|
6338
|
+
return;
|
|
6339
|
+
}
|
|
6340
|
+
if (status === "WAITING_ON_INPUT" && active.observedTurnEvidence) {
|
|
6341
|
+
this.completeActiveTurn({
|
|
6342
|
+
runtime: active.runtime,
|
|
6343
|
+
stopReason: null,
|
|
6344
|
+
runIds: [...active.runIds]
|
|
6345
|
+
});
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
handleTurnTerminalDelta(delta, sdkMessage) {
|
|
6349
|
+
const active = this.activeTurn;
|
|
6350
|
+
if (!active)
|
|
6351
|
+
return;
|
|
6352
|
+
const messageType = streamDeltaMessageType2(delta);
|
|
6353
|
+
if (messageType === "stop_reason") {
|
|
6354
|
+
const stopReason = streamDeltaStopReason2(delta) ?? null;
|
|
6355
|
+
if (stopReason === "requires_approval") {
|
|
6356
|
+
active.observedRequiresApprovalStop = true;
|
|
6357
|
+
return;
|
|
6358
|
+
}
|
|
6359
|
+
this.completeActiveTurn({
|
|
6360
|
+
runtime: active.runtime,
|
|
6361
|
+
stopReason,
|
|
6362
|
+
runIds: [...active.runIds]
|
|
6363
|
+
});
|
|
6364
|
+
return;
|
|
6365
|
+
}
|
|
6366
|
+
if (sdkMessage?.type === "error") {
|
|
6367
|
+
this.completeActiveTurn({
|
|
6368
|
+
runtime: active.runtime,
|
|
6369
|
+
stopReason: sdkMessage.stopReason,
|
|
6370
|
+
runIds: [...active.runIds],
|
|
6371
|
+
success: false,
|
|
6372
|
+
detail: sdkMessage.errorDetail ?? sdkMessage.message,
|
|
6373
|
+
errorCode: sdkMessage.errorCode
|
|
6374
|
+
});
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
transformStreamDelta(delta) {
|
|
6378
|
+
const messageType = typeof delta.message_type === "string" ? delta.message_type : undefined;
|
|
6379
|
+
const runId = typeof delta.run_id === "string" ? delta.run_id : undefined;
|
|
6380
|
+
const uuid = typeof delta.id === "string" ? delta.id : `${this.label}-${++this.messageCounter}`;
|
|
6381
|
+
if (messageType === "assistant_message") {
|
|
6382
|
+
const content = extractTextFromContent(delta.content);
|
|
6383
|
+
if (!content)
|
|
6384
|
+
return null;
|
|
6385
|
+
if (this.activeTurn)
|
|
6386
|
+
this.activeTurn.assistantText += content;
|
|
6387
|
+
return { type: "assistant", content, uuid, runId };
|
|
6388
|
+
}
|
|
6389
|
+
if (messageType === "reasoning_message") {
|
|
6390
|
+
const content = typeof delta.reasoning === "string" ? delta.reasoning : extractTextFromContent(delta.content);
|
|
6391
|
+
if (!content)
|
|
6392
|
+
return null;
|
|
6393
|
+
return { type: "reasoning", content, uuid, runId };
|
|
6394
|
+
}
|
|
6395
|
+
if (messageType === "tool_call_message" || messageType === "approval_request_message") {
|
|
6396
|
+
const toolCall = firstToolCall(delta);
|
|
6397
|
+
if (!toolCall)
|
|
6398
|
+
return null;
|
|
6399
|
+
const fn = toolCall.function && typeof toolCall.function === "object" ? toolCall.function : undefined;
|
|
6400
|
+
const toolCallId = (typeof toolCall.tool_call_id === "string" ? toolCall.tool_call_id : undefined) ?? (typeof toolCall.id === "string" ? toolCall.id : undefined);
|
|
6401
|
+
if (!toolCallId) {
|
|
6402
|
+
const detail = `Missing tool_call_id in ${messageType} (uuid=${uuid})`;
|
|
6403
|
+
return {
|
|
6404
|
+
type: "error",
|
|
6405
|
+
message: detail,
|
|
6406
|
+
errorCode: "protocol_error",
|
|
6407
|
+
stopReason: "protocol_error",
|
|
6408
|
+
runId,
|
|
6409
|
+
recoverable: false,
|
|
6410
|
+
errorDetail: detail
|
|
6411
|
+
};
|
|
6412
|
+
}
|
|
6413
|
+
const toolName = (typeof toolCall.name === "string" ? toolCall.name : undefined) ?? (typeof fn?.name === "string" ? fn.name : undefined) ?? "?";
|
|
6414
|
+
const { input, raw } = toolInputFromArguments(toolCall.arguments ?? fn?.arguments);
|
|
6415
|
+
return {
|
|
6416
|
+
type: "tool_call",
|
|
6417
|
+
toolCallId,
|
|
6418
|
+
toolName,
|
|
6419
|
+
toolInput: input,
|
|
6420
|
+
rawArguments: raw,
|
|
6421
|
+
uuid,
|
|
6422
|
+
runId
|
|
6423
|
+
};
|
|
6424
|
+
}
|
|
6425
|
+
if (messageType === "tool_return_message") {
|
|
6426
|
+
const toolReturn = firstToolReturn(delta) ?? delta;
|
|
6427
|
+
const toolCallId = (typeof delta.tool_call_id === "string" ? delta.tool_call_id : undefined) ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
|
|
6428
|
+
if (!toolCallId)
|
|
6429
|
+
return null;
|
|
6430
|
+
const content = extractTextFromContent(delta.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
|
|
6431
|
+
const status = typeof delta.status === "string" ? delta.status : toolReturn.status;
|
|
6432
|
+
return {
|
|
6433
|
+
type: "tool_result",
|
|
6434
|
+
toolCallId,
|
|
6435
|
+
content,
|
|
6436
|
+
isError: status === "error",
|
|
6437
|
+
uuid,
|
|
6438
|
+
runId
|
|
6439
|
+
};
|
|
6440
|
+
}
|
|
6441
|
+
if (messageType === "error_message" || messageType === "loop_error") {
|
|
6442
|
+
const detail = (typeof delta.detail === "string" ? delta.detail : undefined) ?? (typeof delta.message === "string" ? delta.message : undefined) ?? `${this.label} turn failed`;
|
|
6443
|
+
const stopReason = (typeof delta.stop_reason === "string" ? delta.stop_reason : undefined) ?? (typeof delta.error_type === "string" ? delta.error_type : undefined) ?? "error";
|
|
6444
|
+
const approvalConflict = isApprovalConflictSignal({
|
|
6445
|
+
detail,
|
|
6446
|
+
message: typeof delta.message === "string" ? delta.message : undefined,
|
|
6447
|
+
stopReason
|
|
6448
|
+
});
|
|
6449
|
+
return {
|
|
6450
|
+
type: "error",
|
|
6451
|
+
message: detail,
|
|
6452
|
+
errorCode: approvalConflict ? "approval_conflict" : toSdkErrorCode(stopReason),
|
|
6453
|
+
approvalConflict: approvalConflict || undefined,
|
|
6454
|
+
recoverable: approvalConflict ? true : false,
|
|
6455
|
+
errorDetail: detail,
|
|
6456
|
+
stopReason,
|
|
6457
|
+
runId
|
|
6458
|
+
};
|
|
6459
|
+
}
|
|
6460
|
+
if (messageType === "retry") {
|
|
6461
|
+
return {
|
|
6462
|
+
type: "retry",
|
|
6463
|
+
reason: typeof delta.reason === "string" ? delta.reason : "error",
|
|
6464
|
+
attempt: typeof delta.attempt === "number" ? delta.attempt : 0,
|
|
6465
|
+
maxAttempts: typeof delta.max_attempts === "number" ? delta.max_attempts : 0,
|
|
6466
|
+
delayMs: typeof delta.delay_ms === "number" ? delta.delay_ms : 0,
|
|
6467
|
+
runId
|
|
6468
|
+
};
|
|
6469
|
+
}
|
|
6470
|
+
if (messageType === "stop_reason" || messageType === "ping") {
|
|
6471
|
+
return null;
|
|
6472
|
+
}
|
|
6473
|
+
return {
|
|
6474
|
+
type: "stream_event",
|
|
6475
|
+
event: delta,
|
|
6476
|
+
uuid
|
|
6477
|
+
};
|
|
6478
|
+
}
|
|
6479
|
+
resultFromTurn(turn, tracker) {
|
|
6480
|
+
const stopReason = turn.stopReason ?? (turn.success === false ? "error" : undefined);
|
|
6481
|
+
const approvalConflict = isApprovalConflictSignal({
|
|
6482
|
+
detail: turn.detail,
|
|
6483
|
+
stopReason
|
|
6484
|
+
});
|
|
6485
|
+
const success = turn.success !== undefined ? turn.success && !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "") : !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "");
|
|
6486
|
+
const errorCode = approvalConflict ? "approval_conflict" : turn.errorCode ?? toSdkErrorCode(stopReason);
|
|
6487
|
+
return {
|
|
6488
|
+
type: "result",
|
|
6489
|
+
success,
|
|
6490
|
+
result: success ? tracker?.assistantText || undefined : undefined,
|
|
6491
|
+
error: success ? undefined : errorCode ?? stopReason ?? "error",
|
|
6492
|
+
errorCode: success ? undefined : errorCode ?? "error",
|
|
6493
|
+
approvalConflict: approvalConflict || undefined,
|
|
6494
|
+
recoverable: approvalConflict ? true : success ? undefined : false,
|
|
6495
|
+
errorDetail: success ? undefined : turn.detail,
|
|
6496
|
+
stopReason,
|
|
6497
|
+
durationMs: Date.now() - (tracker?.startedAt || this._activeTurnStartedAt),
|
|
6498
|
+
conversationId: turn.runtime.conversation_id,
|
|
6499
|
+
runIds: turn.runIds.length > 0 ? turn.runIds : undefined
|
|
6500
|
+
};
|
|
6501
|
+
}
|
|
6502
|
+
enqueue(message) {
|
|
6503
|
+
const resolver = this.streamResolvers.shift();
|
|
6504
|
+
if (resolver) {
|
|
6505
|
+
resolver(message);
|
|
6506
|
+
return;
|
|
6507
|
+
}
|
|
6508
|
+
this.streamQueue.push(message);
|
|
6509
|
+
}
|
|
6510
|
+
resolveAll(value) {
|
|
6511
|
+
for (const resolve of this.streamResolvers.splice(0)) {
|
|
6512
|
+
resolve(value);
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
// src/remote-client-session-core.ts
|
|
6517
|
+
class RemoteClientSessionCore {
|
|
6518
|
+
mode;
|
|
6519
|
+
controller = null;
|
|
6520
|
+
runtime = null;
|
|
6521
|
+
initialized = false;
|
|
6522
|
+
closed = false;
|
|
6523
|
+
_agentId = null;
|
|
6524
|
+
_sessionId = null;
|
|
6525
|
+
_conversationId = null;
|
|
6526
|
+
_model = "";
|
|
6527
|
+
_modelSettings = null;
|
|
6528
|
+
label;
|
|
6529
|
+
requestTimeoutMs;
|
|
6530
|
+
initializePromise = null;
|
|
6531
|
+
removeMessageHandler = null;
|
|
6532
|
+
turns;
|
|
6533
|
+
toolNames;
|
|
6534
|
+
deviceStatusListeners = new Set;
|
|
6535
|
+
deviceStatusRefreshCancels = new Set;
|
|
6536
|
+
constructor(mode, config) {
|
|
6537
|
+
this.mode = mode;
|
|
6538
|
+
this.label = config.label;
|
|
6539
|
+
this.requestTimeoutMs = config.requestTimeoutMs;
|
|
6540
|
+
this.turns = new RemoteTurnCoordinator({
|
|
6541
|
+
label: config.label,
|
|
6542
|
+
requestTimeoutMs: config.requestTimeoutMs,
|
|
6543
|
+
onDeviceStatus: (status) => this.emitDeviceStatus(status)
|
|
6544
|
+
});
|
|
6545
|
+
}
|
|
6546
|
+
async initialize() {
|
|
6547
|
+
if (this.closed) {
|
|
6548
|
+
throw new Error("Session is closed");
|
|
6066
6549
|
}
|
|
6067
|
-
if (
|
|
6068
|
-
|
|
6550
|
+
if (this.initializePromise) {
|
|
6551
|
+
return this.initializePromise;
|
|
6069
6552
|
}
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
const result = await this.controller.updateModel(this.runtime, payload);
|
|
6073
|
-
if (result.modelHandle !== undefined) {
|
|
6074
|
-
this._model = result.modelHandle;
|
|
6075
|
-
} else if (payload.model_handle !== undefined) {
|
|
6076
|
-
this._model = payload.model_handle;
|
|
6077
|
-
} else if (typeof result.modelSettings?.model === "string") {
|
|
6078
|
-
this._model = result.modelSettings.model;
|
|
6553
|
+
if (this.initialized) {
|
|
6554
|
+
throw new Error("Session already initialized");
|
|
6079
6555
|
}
|
|
6080
|
-
|
|
6081
|
-
|
|
6556
|
+
const attempt = this.performInitialize();
|
|
6557
|
+
const memo = attempt.catch((error) => {
|
|
6558
|
+
this.cleanupFailedInitialize();
|
|
6559
|
+
throw error;
|
|
6560
|
+
}).finally(() => {
|
|
6561
|
+
if (this.initializePromise === memo) {
|
|
6562
|
+
this.initializePromise = null;
|
|
6563
|
+
}
|
|
6564
|
+
});
|
|
6565
|
+
this.initializePromise = memo;
|
|
6566
|
+
return memo;
|
|
6567
|
+
}
|
|
6568
|
+
async performInitialize() {
|
|
6569
|
+
const init = await this.initializeRuntimeController();
|
|
6570
|
+
this.controller = init.controller;
|
|
6571
|
+
this.runtime = init.runtime;
|
|
6572
|
+
this._agentId = init.runtime.agent_id;
|
|
6573
|
+
this._conversationId = init.runtime.conversation_id;
|
|
6574
|
+
this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
|
|
6575
|
+
this._modelSettings = init.modelSettings ?? null;
|
|
6576
|
+
this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
|
|
6577
|
+
this.toolNames = init.tools;
|
|
6578
|
+
this.removeMessageHandler = this.controller.onMessage((message) => {
|
|
6579
|
+
if (this.runtime)
|
|
6580
|
+
this.turns.handleProtocolMessage(message, this.runtime);
|
|
6581
|
+
});
|
|
6582
|
+
await this.afterRuntimeInitialized();
|
|
6583
|
+
await this.applyPostInitializeOptions();
|
|
6584
|
+
if (this.closed) {
|
|
6585
|
+
throw new Error("Session is closed");
|
|
6082
6586
|
}
|
|
6083
|
-
|
|
6587
|
+
this.initialized = true;
|
|
6588
|
+
const initMessage = {
|
|
6589
|
+
type: "init",
|
|
6590
|
+
agentId: init.runtime.agent_id,
|
|
6591
|
+
sessionId: this._sessionId,
|
|
6592
|
+
conversationId: init.runtime.conversation_id,
|
|
6593
|
+
model: this._model
|
|
6594
|
+
};
|
|
6595
|
+
if (this.toolNames !== undefined)
|
|
6596
|
+
initMessage.tools = this.toolNames;
|
|
6597
|
+
if (init.skillSources !== undefined) {
|
|
6598
|
+
initMessage.skillSources = init.skillSources;
|
|
6599
|
+
}
|
|
6600
|
+
return initMessage;
|
|
6084
6601
|
}
|
|
6085
|
-
|
|
6602
|
+
cleanupFailedInitialize() {
|
|
6603
|
+
this.removeMessageHandler?.();
|
|
6604
|
+
this.removeMessageHandler = null;
|
|
6605
|
+
this.controller?.close();
|
|
6606
|
+
this.controller = null;
|
|
6607
|
+
this.onCoreClose();
|
|
6608
|
+
this.runtime = null;
|
|
6609
|
+
this._agentId = null;
|
|
6610
|
+
this._conversationId = null;
|
|
6611
|
+
this._sessionId = null;
|
|
6612
|
+
this._modelSettings = null;
|
|
6613
|
+
this._model = "";
|
|
6614
|
+
this.toolNames = undefined;
|
|
6615
|
+
this.initialized = false;
|
|
6616
|
+
}
|
|
6617
|
+
async send(message) {
|
|
6086
6618
|
if (!this.initialized) {
|
|
6087
6619
|
await this.initialize();
|
|
6088
6620
|
}
|
|
6089
6621
|
if (!this.controller || !this.runtime) {
|
|
6090
6622
|
throw new Error("Session is not initialized");
|
|
6091
6623
|
}
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6624
|
+
await this.beforeTurn();
|
|
6625
|
+
const turn = this.turns.trackSentTurn(this.runtime);
|
|
6626
|
+
try {
|
|
6627
|
+
this.controller.sendTurnMessage(this.runtime, message, {
|
|
6628
|
+
clientMessageId: turn.clientMessageId
|
|
6629
|
+
});
|
|
6630
|
+
} catch (error) {
|
|
6631
|
+
this.turns.removeTrackedTurn(turn);
|
|
6632
|
+
throw error;
|
|
6100
6633
|
}
|
|
6101
|
-
|
|
6102
|
-
|
|
6634
|
+
}
|
|
6635
|
+
async runTurn(message, _options = {}) {
|
|
6636
|
+
if (this.turns.hasInFlightTurn()) {
|
|
6637
|
+
throw new Error(`A turn is already in flight for this ${this.label} session. Use send() and stream() to let the listener queue messages.`);
|
|
6103
6638
|
}
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
});
|
|
6110
|
-
if (typeof response.success !== "boolean") {
|
|
6111
|
-
throw new Error("Invalid remove_queue_item_response from runtime");
|
|
6639
|
+
await this.send(message);
|
|
6640
|
+
for await (const msg of this.stream()) {
|
|
6641
|
+
if (msg.type === "result") {
|
|
6642
|
+
return msg;
|
|
6643
|
+
}
|
|
6112
6644
|
}
|
|
6113
6645
|
return {
|
|
6114
|
-
|
|
6115
|
-
|
|
6646
|
+
type: "result",
|
|
6647
|
+
success: false,
|
|
6648
|
+
error: "stream_closed",
|
|
6649
|
+
errorCode: "stream_closed",
|
|
6650
|
+
recoverable: false,
|
|
6651
|
+
errorDetail: "Stream ended before terminal result",
|
|
6652
|
+
durationMs: Date.now() - this.turns.activeTurnStartedAt,
|
|
6653
|
+
conversationId: this._conversationId
|
|
6116
6654
|
};
|
|
6117
6655
|
}
|
|
6118
|
-
async
|
|
6656
|
+
async* stream() {
|
|
6657
|
+
while (true) {
|
|
6658
|
+
const msg = await this.turns.nextMessage();
|
|
6659
|
+
if (!msg)
|
|
6660
|
+
break;
|
|
6661
|
+
yield msg;
|
|
6662
|
+
if (msg.type === "result")
|
|
6663
|
+
break;
|
|
6664
|
+
}
|
|
6665
|
+
}
|
|
6666
|
+
async abort() {
|
|
6667
|
+
if (!this.initialized)
|
|
6668
|
+
return;
|
|
6669
|
+
if (!this.controller || !this.runtime)
|
|
6670
|
+
return;
|
|
6671
|
+
this.turns.markAbortRequested();
|
|
6672
|
+
await this.controller.abort(this.runtime);
|
|
6673
|
+
}
|
|
6674
|
+
async sendCommand(command, options) {
|
|
6119
6675
|
if (!this.initialized) {
|
|
6120
6676
|
await this.initialize();
|
|
6121
6677
|
}
|
|
6122
|
-
if (!this.controller
|
|
6678
|
+
if (!this.controller) {
|
|
6123
6679
|
throw new Error("Session is not initialized");
|
|
6124
6680
|
}
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
throw new Error("Invalid device status timeout. Expected a positive integer.");
|
|
6681
|
+
if (!command || typeof command !== "object" || Array.isArray(command)) {
|
|
6682
|
+
throw new Error("Invalid command. Expected a protocol command object.");
|
|
6128
6683
|
}
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
onDeviceStatus(listener) {
|
|
6132
|
-
if (typeof listener !== "function") {
|
|
6133
|
-
throw new Error("Invalid device status listener. Expected a function.");
|
|
6684
|
+
if (typeof command.type !== "string" || command.type.length === 0) {
|
|
6685
|
+
throw new Error("Invalid command. Expected a non-empty type.");
|
|
6134
6686
|
}
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
};
|
|
6139
|
-
}
|
|
6140
|
-
refreshDeviceStatus(timeoutMs) {
|
|
6141
|
-
const controller = this.controller;
|
|
6142
|
-
const runtime = this.runtime;
|
|
6143
|
-
if (!controller || !runtime) {
|
|
6144
|
-
return Promise.reject(new Error("Session is not initialized"));
|
|
6687
|
+
if (!options || !options.responseType && !options.predicate && options.timeoutMs === undefined) {
|
|
6688
|
+
this.controller.send(command);
|
|
6689
|
+
return;
|
|
6145
6690
|
}
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
const cleanup = () => {
|
|
6151
|
-
clearTimeout(timer);
|
|
6152
|
-
unsubscribe();
|
|
6153
|
-
this.deviceStatusRefreshCancels.delete(cancel);
|
|
6154
|
-
};
|
|
6155
|
-
const rejectOnce = (error) => {
|
|
6156
|
-
if (settled)
|
|
6157
|
-
return;
|
|
6158
|
-
settled = true;
|
|
6159
|
-
cleanup();
|
|
6160
|
-
reject(error);
|
|
6161
|
-
};
|
|
6162
|
-
const resolveIfComplete = () => {
|
|
6163
|
-
if (settled || !syncAcknowledged || !status)
|
|
6164
|
-
return;
|
|
6165
|
-
settled = true;
|
|
6166
|
-
cleanup();
|
|
6167
|
-
resolve(status);
|
|
6168
|
-
};
|
|
6169
|
-
const cancel = (error) => rejectOnce(error);
|
|
6170
|
-
const timer = setTimeout(() => {
|
|
6171
|
-
rejectOnce(new Error(`Timed out waiting for ${this.label} device status`));
|
|
6172
|
-
}, timeoutMs);
|
|
6173
|
-
timer.unref?.();
|
|
6174
|
-
const unsubscribe = this.onDeviceStatus((nextStatus) => {
|
|
6175
|
-
status = nextStatus;
|
|
6176
|
-
resolveIfComplete();
|
|
6177
|
-
});
|
|
6178
|
-
this.deviceStatusRefreshCancels.add(cancel);
|
|
6179
|
-
controller.request("sync", {
|
|
6180
|
-
runtime,
|
|
6181
|
-
recover_approvals: false,
|
|
6182
|
-
force_device_status: true
|
|
6183
|
-
}, {
|
|
6184
|
-
timeoutMs,
|
|
6185
|
-
predicate: (message) => message.type === "sync_response"
|
|
6186
|
-
}).then((response) => {
|
|
6187
|
-
if (response.success === false) {
|
|
6188
|
-
rejectOnce(new Error(typeof response.error === "string" ? response.error : `Failed to refresh ${this.label} device status`));
|
|
6189
|
-
return;
|
|
6190
|
-
}
|
|
6191
|
-
syncAcknowledged = true;
|
|
6192
|
-
resolveIfComplete();
|
|
6193
|
-
}, (error) => {
|
|
6194
|
-
rejectOnce(error instanceof Error ? error : new Error(String(error)));
|
|
6195
|
-
});
|
|
6691
|
+
const { type, ...body } = command;
|
|
6692
|
+
const response = await this.controller.request(type, body, {
|
|
6693
|
+
...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {},
|
|
6694
|
+
predicate: options.predicate ? (message) => options.predicate?.(message) === true : options.responseType ? (message) => message.type === options.responseType : undefined
|
|
6196
6695
|
});
|
|
6696
|
+
return response;
|
|
6197
6697
|
}
|
|
6198
|
-
async
|
|
6698
|
+
async listModels() {
|
|
6199
6699
|
if (!this.initialized) {
|
|
6200
6700
|
await this.initialize();
|
|
6201
6701
|
}
|
|
6202
6702
|
if (!this.controller) {
|
|
6203
6703
|
throw new Error("Session is not initialized");
|
|
6204
6704
|
}
|
|
6205
|
-
|
|
6206
|
-
if (!conversationId) {
|
|
6207
|
-
throw new Error("No conversation id available for listMessages()");
|
|
6208
|
-
}
|
|
6209
|
-
return this.controller.listMessages(conversationId, options);
|
|
6705
|
+
return this.controller.listModels();
|
|
6210
6706
|
}
|
|
6211
|
-
async
|
|
6707
|
+
async updateModel(update) {
|
|
6212
6708
|
if (!this.initialized) {
|
|
6213
6709
|
await this.initialize();
|
|
6214
6710
|
}
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
order: options.order
|
|
6218
|
-
});
|
|
6219
|
-
const state = {
|
|
6220
|
-
agentId: this._agentId ?? "",
|
|
6221
|
-
conversationId: this._conversationId ?? "",
|
|
6222
|
-
model: this._model,
|
|
6223
|
-
messages: page.messages
|
|
6224
|
-
};
|
|
6225
|
-
if (this.toolNames !== undefined) {
|
|
6226
|
-
state.tools = this.toolNames;
|
|
6227
|
-
}
|
|
6228
|
-
if (page.nextBefore !== undefined) {
|
|
6229
|
-
state.nextBefore = page.nextBefore;
|
|
6230
|
-
}
|
|
6231
|
-
if (page.hasMore !== undefined) {
|
|
6232
|
-
state.hasMore = page.hasMore;
|
|
6711
|
+
if (!this.controller || !this.runtime) {
|
|
6712
|
+
throw new Error("Session is not initialized");
|
|
6233
6713
|
}
|
|
6234
|
-
return
|
|
6714
|
+
return this.applyModelUpdate(update);
|
|
6235
6715
|
}
|
|
6236
|
-
|
|
6237
|
-
if (this.
|
|
6238
|
-
|
|
6239
|
-
this.closed = true;
|
|
6240
|
-
this.removeMessageHandler?.();
|
|
6241
|
-
this.removeMessageHandler = null;
|
|
6242
|
-
if (this.activeTurn?.timeout)
|
|
6243
|
-
clearTimeout(this.activeTurn.timeout);
|
|
6244
|
-
for (const turn of this.pendingTurns) {
|
|
6245
|
-
if (turn.timeout)
|
|
6246
|
-
clearTimeout(turn.timeout);
|
|
6247
|
-
}
|
|
6248
|
-
this.activeTurn = null;
|
|
6249
|
-
this.pendingTurns.length = 0;
|
|
6250
|
-
for (const cancel of [...this.deviceStatusRefreshCancels]) {
|
|
6251
|
-
cancel(new Error(`Session closed while waiting for ${this.label} device status`));
|
|
6716
|
+
async applyModelUpdate(update) {
|
|
6717
|
+
if (!this.controller || !this.runtime) {
|
|
6718
|
+
throw new Error("Session is not initialized");
|
|
6252
6719
|
}
|
|
6253
|
-
|
|
6254
|
-
this.
|
|
6255
|
-
this.controller
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
return this._conversationId;
|
|
6268
|
-
}
|
|
6269
|
-
async[Symbol.asyncDispose]() {
|
|
6270
|
-
this.close();
|
|
6720
|
+
const normalized = normalizeUpdateModelInput(update);
|
|
6721
|
+
const payload = await this.resolveUpdateModelPayload(normalized);
|
|
6722
|
+
const result = await this.controller.updateModel(this.runtime, payload);
|
|
6723
|
+
if (result.modelHandle !== undefined) {
|
|
6724
|
+
this._model = result.modelHandle;
|
|
6725
|
+
} else if (payload.model_handle !== undefined) {
|
|
6726
|
+
this._model = payload.model_handle;
|
|
6727
|
+
} else if (typeof result.modelSettings?.model === "string") {
|
|
6728
|
+
this._model = result.modelSettings.model;
|
|
6729
|
+
}
|
|
6730
|
+
if ("modelSettings" in result) {
|
|
6731
|
+
this._modelSettings = result.modelSettings ?? null;
|
|
6732
|
+
}
|
|
6733
|
+
return result;
|
|
6271
6734
|
}
|
|
6272
|
-
async
|
|
6735
|
+
async recoverPendingApprovals(options = {}) {
|
|
6273
6736
|
if (!this.initialized) {
|
|
6274
6737
|
await this.initialize();
|
|
6275
6738
|
}
|
|
6276
6739
|
if (!this.controller || !this.runtime) {
|
|
6277
6740
|
throw new Error("Session is not initialized");
|
|
6278
6741
|
}
|
|
6279
|
-
|
|
6280
|
-
if (updates.cwd !== undefined)
|
|
6281
|
-
payload.cwd = updates.cwd;
|
|
6282
|
-
if (updates.permissionMode !== undefined) {
|
|
6283
|
-
const mode = mapPermissionMode(updates.permissionMode);
|
|
6284
|
-
if (mode !== undefined)
|
|
6285
|
-
payload.mode = mode;
|
|
6286
|
-
}
|
|
6287
|
-
if (Object.keys(payload).length === 0) {
|
|
6288
|
-
throw new Error("Invalid device state update. Expected cwd or permissionMode.");
|
|
6289
|
-
}
|
|
6290
|
-
this.controller.send({
|
|
6291
|
-
type: "change_device_state",
|
|
6292
|
-
runtime: this.runtime,
|
|
6293
|
-
payload
|
|
6294
|
-
});
|
|
6742
|
+
return this.controller.recoverPendingApprovals(this.runtime, options);
|
|
6295
6743
|
}
|
|
6296
|
-
async
|
|
6744
|
+
async removeQueuedMessage(itemId) {
|
|
6745
|
+
if (typeof itemId !== "string" || itemId.trim().length === 0) {
|
|
6746
|
+
throw new Error("Invalid queue item id. Expected a non-empty string.");
|
|
6747
|
+
}
|
|
6297
6748
|
if (!this.initialized) {
|
|
6298
6749
|
await this.initialize();
|
|
6299
6750
|
}
|
|
6300
6751
|
if (!this.controller || !this.runtime) {
|
|
6301
6752
|
throw new Error("Session is not initialized");
|
|
6302
6753
|
}
|
|
6303
|
-
const response = await this.controller.request("
|
|
6754
|
+
const response = await this.controller.request("remove_queue_item", {
|
|
6304
6755
|
runtime: this.runtime,
|
|
6305
|
-
|
|
6306
|
-
}, {
|
|
6307
|
-
|
|
6308
|
-
}
|
|
6309
|
-
async afterRuntimeInitialized() {}
|
|
6310
|
-
async beforeTurn() {}
|
|
6311
|
-
onCoreClose() {}
|
|
6312
|
-
currentOptions() {
|
|
6313
|
-
return this.mode.options;
|
|
6314
|
-
}
|
|
6315
|
-
shouldEnableMemfs(options) {
|
|
6316
|
-
return false;
|
|
6317
|
-
}
|
|
6318
|
-
enableMemfsBody() {
|
|
6319
|
-
if (!this.runtime)
|
|
6320
|
-
return {};
|
|
6321
|
-
return { agent_id: this.runtime.agent_id };
|
|
6322
|
-
}
|
|
6323
|
-
setModel(model) {
|
|
6324
|
-
this._model = model;
|
|
6325
|
-
}
|
|
6326
|
-
trackSentTurn(runtime) {
|
|
6327
|
-
const turn = {
|
|
6328
|
-
id: ++this.nextTurnId,
|
|
6329
|
-
runtime,
|
|
6330
|
-
clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
|
|
6331
|
-
queuedAt: Date.now(),
|
|
6332
|
-
startedAt: 0,
|
|
6333
|
-
assistantText: "",
|
|
6334
|
-
runIds: new Set,
|
|
6335
|
-
observedTurnEvidence: false,
|
|
6336
|
-
observedRequiresApprovalStop: false,
|
|
6337
|
-
abortRequested: false,
|
|
6338
|
-
timeout: null
|
|
6339
|
-
};
|
|
6340
|
-
if (this.activeTurn) {
|
|
6341
|
-
this.pendingTurns.push(turn);
|
|
6342
|
-
} else {
|
|
6343
|
-
this.activateTurn(turn);
|
|
6344
|
-
}
|
|
6345
|
-
return turn;
|
|
6346
|
-
}
|
|
6347
|
-
activateTurn(turn) {
|
|
6348
|
-
this.activeTurn = turn;
|
|
6349
|
-
turn.startedAt = Date.now();
|
|
6350
|
-
this.activeTurnStartedAt = turn.startedAt;
|
|
6351
|
-
if (this.requestTimeoutMs !== undefined) {
|
|
6352
|
-
turn.timeout = setTimeout(() => {
|
|
6353
|
-
this.failTurn(turn, `Timed out waiting for ${this.label} turn`);
|
|
6354
|
-
}, this.requestTimeoutMs);
|
|
6355
|
-
turn.timeout.unref?.();
|
|
6356
|
-
}
|
|
6357
|
-
}
|
|
6358
|
-
activateNextTurnFromProtocol() {
|
|
6359
|
-
if (this.activeTurn)
|
|
6360
|
-
return this.activeTurn;
|
|
6361
|
-
const next = this.pendingTurns.shift();
|
|
6362
|
-
if (!next)
|
|
6363
|
-
return null;
|
|
6364
|
-
this.activateTurn(next);
|
|
6365
|
-
return next;
|
|
6366
|
-
}
|
|
6367
|
-
removeTrackedTurn(turn) {
|
|
6368
|
-
if (turn.timeout)
|
|
6369
|
-
clearTimeout(turn.timeout);
|
|
6370
|
-
if (this.activeTurn === turn) {
|
|
6371
|
-
this.activeTurn = null;
|
|
6372
|
-
return;
|
|
6373
|
-
}
|
|
6374
|
-
const index = this.pendingTurns.indexOf(turn);
|
|
6375
|
-
if (index !== -1)
|
|
6376
|
-
this.pendingTurns.splice(index, 1);
|
|
6377
|
-
}
|
|
6378
|
-
failTurn(turn, detail) {
|
|
6379
|
-
if (this.activeTurn !== turn)
|
|
6380
|
-
return;
|
|
6381
|
-
this.enqueue({
|
|
6382
|
-
type: "error",
|
|
6383
|
-
message: detail,
|
|
6384
|
-
errorCode: "error",
|
|
6385
|
-
stopReason: "error",
|
|
6386
|
-
errorDetail: detail,
|
|
6387
|
-
recoverable: false
|
|
6388
|
-
});
|
|
6389
|
-
this.completeActiveTurn({
|
|
6390
|
-
runtime: turn.runtime,
|
|
6391
|
-
stopReason: "error",
|
|
6392
|
-
runIds: [...turn.runIds],
|
|
6393
|
-
success: false,
|
|
6394
|
-
detail,
|
|
6395
|
-
errorCode: "error"
|
|
6756
|
+
item_id: itemId
|
|
6757
|
+
}, {
|
|
6758
|
+
predicate: (message) => message.type === "remove_queue_item_response" && message.item_id === itemId
|
|
6396
6759
|
});
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
const active = this.activeTurn;
|
|
6400
|
-
if (!active)
|
|
6401
|
-
return;
|
|
6402
|
-
if (active.timeout) {
|
|
6403
|
-
clearTimeout(active.timeout);
|
|
6404
|
-
active.timeout = null;
|
|
6760
|
+
if (typeof response.success !== "boolean") {
|
|
6761
|
+
throw new Error("Invalid remove_queue_item_response from runtime");
|
|
6405
6762
|
}
|
|
6406
|
-
|
|
6407
|
-
|
|
6763
|
+
return {
|
|
6764
|
+
itemId: typeof response.item_id === "string" ? response.item_id : itemId,
|
|
6765
|
+
removed: response.success
|
|
6766
|
+
};
|
|
6408
6767
|
}
|
|
6409
|
-
async
|
|
6410
|
-
if (
|
|
6411
|
-
|
|
6768
|
+
async getDeviceStatus(options = {}) {
|
|
6769
|
+
if (!this.initialized) {
|
|
6770
|
+
await this.initialize();
|
|
6412
6771
|
}
|
|
6413
|
-
if (!this.controller) {
|
|
6772
|
+
if (!this.controller || !this.runtime) {
|
|
6414
6773
|
throw new Error("Session is not initialized");
|
|
6415
6774
|
}
|
|
6416
|
-
const
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
let baseEntry;
|
|
6420
|
-
let explicitHandle;
|
|
6421
|
-
let targetHandle;
|
|
6422
|
-
if (input.modelId !== undefined) {
|
|
6423
|
-
baseEntry = byId.get(input.modelId);
|
|
6424
|
-
explicitHandle = input.modelHandle;
|
|
6425
|
-
targetHandle = baseEntry?.handle ?? toBaseModelHandle(input.modelHandle, aliases);
|
|
6426
|
-
} else if (input.modelHandle !== undefined) {
|
|
6427
|
-
explicitHandle = input.modelHandle;
|
|
6428
|
-
targetHandle = toBaseModelHandle(input.modelHandle, aliases);
|
|
6429
|
-
} else if (input.model !== undefined) {
|
|
6430
|
-
baseEntry = byId.get(input.model);
|
|
6431
|
-
if (baseEntry) {
|
|
6432
|
-
targetHandle = baseEntry.handle;
|
|
6433
|
-
} else {
|
|
6434
|
-
explicitHandle = input.model;
|
|
6435
|
-
targetHandle = toBaseModelHandle(input.model, aliases);
|
|
6436
|
-
}
|
|
6437
|
-
} else {
|
|
6438
|
-
explicitHandle = this._model || undefined;
|
|
6439
|
-
targetHandle = toBaseModelHandle(this._model || undefined, aliases);
|
|
6440
|
-
}
|
|
6441
|
-
if (!targetHandle) {
|
|
6442
|
-
throw new Error("reasoningEffort requires a current model or explicit model/modelId/modelHandle.");
|
|
6443
|
-
}
|
|
6444
|
-
const candidates = catalog.entries.filter((entry) => entry.handle === targetHandle || entry.handle === explicitHandle);
|
|
6445
|
-
if (candidates.length === 0) {
|
|
6446
|
-
throw new Error(`reasoningEffort requires a model from listModels(); no catalog entry found for ${targetHandle}.`);
|
|
6447
|
-
}
|
|
6448
|
-
const contextWindow = getContextWindow(baseEntry?.updateArgs) ?? getContextWindow(this._modelSettings);
|
|
6449
|
-
const scopedCandidates = sameContextCandidates(candidates, contextWindow);
|
|
6450
|
-
const matchingEntry = scopedCandidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort) ?? candidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort);
|
|
6451
|
-
if (!matchingEntry) {
|
|
6452
|
-
throw new Error(`No ${input.reasoningEffort} reasoning tier found for model ${targetHandle}.`);
|
|
6775
|
+
const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs ?? 30000;
|
|
6776
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
6777
|
+
throw new Error("Invalid device status timeout. Expected a positive integer.");
|
|
6453
6778
|
}
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6779
|
+
return this.refreshDeviceStatus(timeoutMs);
|
|
6780
|
+
}
|
|
6781
|
+
onDeviceStatus(listener) {
|
|
6782
|
+
if (typeof listener !== "function") {
|
|
6783
|
+
throw new Error("Invalid device status listener. Expected a function.");
|
|
6457
6784
|
}
|
|
6458
|
-
|
|
6785
|
+
this.deviceStatusListeners.add(listener);
|
|
6786
|
+
return () => {
|
|
6787
|
+
this.deviceStatusListeners.delete(listener);
|
|
6788
|
+
};
|
|
6459
6789
|
}
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6790
|
+
refreshDeviceStatus(timeoutMs) {
|
|
6791
|
+
const controller = this.controller;
|
|
6792
|
+
const runtime = this.runtime;
|
|
6793
|
+
if (!controller || !runtime) {
|
|
6794
|
+
return Promise.reject(new Error("Session is not initialized"));
|
|
6795
|
+
}
|
|
6796
|
+
return new Promise((resolve, reject) => {
|
|
6797
|
+
let status = null;
|
|
6798
|
+
let syncAcknowledged = false;
|
|
6799
|
+
let settled = false;
|
|
6800
|
+
const cleanup = () => {
|
|
6801
|
+
clearTimeout(timer);
|
|
6802
|
+
unsubscribe();
|
|
6803
|
+
this.deviceStatusRefreshCancels.delete(cancel);
|
|
6804
|
+
};
|
|
6805
|
+
const rejectOnce = (error) => {
|
|
6806
|
+
if (settled)
|
|
6807
|
+
return;
|
|
6808
|
+
settled = true;
|
|
6809
|
+
cleanup();
|
|
6810
|
+
reject(error);
|
|
6811
|
+
};
|
|
6812
|
+
const resolveIfComplete = () => {
|
|
6813
|
+
if (settled || !syncAcknowledged || !status)
|
|
6814
|
+
return;
|
|
6815
|
+
settled = true;
|
|
6816
|
+
cleanup();
|
|
6817
|
+
resolve(status);
|
|
6818
|
+
};
|
|
6819
|
+
const cancel = (error) => rejectOnce(error);
|
|
6820
|
+
const timer = setTimeout(() => {
|
|
6821
|
+
rejectOnce(new Error(`Timed out waiting for ${this.label} device status`));
|
|
6822
|
+
}, timeoutMs);
|
|
6823
|
+
timer.unref?.();
|
|
6824
|
+
const unsubscribe = this.onDeviceStatus((nextStatus) => {
|
|
6825
|
+
status = nextStatus;
|
|
6826
|
+
resolveIfComplete();
|
|
6827
|
+
});
|
|
6828
|
+
this.deviceStatusRefreshCancels.add(cancel);
|
|
6829
|
+
controller.request("sync", {
|
|
6830
|
+
runtime,
|
|
6831
|
+
recover_approvals: false,
|
|
6832
|
+
force_device_status: true
|
|
6833
|
+
}, {
|
|
6834
|
+
timeoutMs,
|
|
6835
|
+
predicate: (message) => message.type === "sync_response"
|
|
6836
|
+
}).then((response) => {
|
|
6837
|
+
if (response.success === false) {
|
|
6838
|
+
rejectOnce(new Error(typeof response.error === "string" ? response.error : `Failed to refresh ${this.label} device status`));
|
|
6839
|
+
return;
|
|
6840
|
+
}
|
|
6841
|
+
syncAcknowledged = true;
|
|
6842
|
+
resolveIfComplete();
|
|
6843
|
+
}, (error) => {
|
|
6844
|
+
rejectOnce(error instanceof Error ? error : new Error(String(error)));
|
|
6468
6845
|
});
|
|
6469
|
-
|
|
6846
|
+
});
|
|
6847
|
+
}
|
|
6848
|
+
async listMessages(options = {}) {
|
|
6849
|
+
if (!this.initialized) {
|
|
6850
|
+
await this.initialize();
|
|
6470
6851
|
}
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
const response = await this.controller.request("set_reflection_settings", {
|
|
6474
|
-
runtime: this.runtime,
|
|
6475
|
-
settings: dreamingSettings,
|
|
6476
|
-
scope: "both"
|
|
6477
|
-
}, { predicate: (message) => message.type === "set_reflection_settings_response" });
|
|
6478
|
-
ensureSuccess(response, "Failed to update dreaming settings");
|
|
6852
|
+
if (!this.controller) {
|
|
6853
|
+
throw new Error("Session is not initialized");
|
|
6479
6854
|
}
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
await this.updateModel({
|
|
6484
|
-
...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
|
|
6485
|
-
...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
|
|
6486
|
-
});
|
|
6855
|
+
const conversationId = options.conversationId ?? this._conversationId;
|
|
6856
|
+
if (!conversationId) {
|
|
6857
|
+
throw new Error("No conversation id available for listMessages()");
|
|
6487
6858
|
}
|
|
6859
|
+
return this.controller.listMessages(conversationId, options);
|
|
6488
6860
|
}
|
|
6489
|
-
|
|
6490
|
-
if (!this.
|
|
6491
|
-
|
|
6492
|
-
const deviceStatus = deviceStatusRecord(message);
|
|
6493
|
-
if (deviceStatus) {
|
|
6494
|
-
const status = toSessionDeviceStatus(deviceStatus);
|
|
6495
|
-
if (!status)
|
|
6496
|
-
return;
|
|
6497
|
-
for (const listener of [...this.deviceStatusListeners]) {
|
|
6498
|
-
try {
|
|
6499
|
-
listener(status);
|
|
6500
|
-
} catch {}
|
|
6501
|
-
}
|
|
6502
|
-
return;
|
|
6503
|
-
}
|
|
6504
|
-
if (message.type === "update_queue") {
|
|
6505
|
-
const sdkMessage2 = {
|
|
6506
|
-
type: "queue_update",
|
|
6507
|
-
queue: queueItems(message)
|
|
6508
|
-
};
|
|
6509
|
-
this.enqueue(sdkMessage2);
|
|
6510
|
-
return;
|
|
6861
|
+
async bootstrapState(options = {}) {
|
|
6862
|
+
if (!this.initialized) {
|
|
6863
|
+
await this.initialize();
|
|
6511
6864
|
}
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6865
|
+
const page = await this.listMessages({
|
|
6866
|
+
limit: options.limit,
|
|
6867
|
+
order: options.order
|
|
6868
|
+
});
|
|
6869
|
+
const state = {
|
|
6870
|
+
agentId: this._agentId ?? "",
|
|
6871
|
+
conversationId: this._conversationId ?? "",
|
|
6872
|
+
model: this._model,
|
|
6873
|
+
messages: page.messages
|
|
6874
|
+
};
|
|
6875
|
+
if (this.toolNames !== undefined) {
|
|
6876
|
+
state.tools = this.toolNames;
|
|
6515
6877
|
}
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
return;
|
|
6519
|
-
const active = this.activateNextTurnFromProtocol();
|
|
6520
|
-
if (active) {
|
|
6521
|
-
active.observedTurnEvidence = true;
|
|
6522
|
-
const runId = streamDeltaRunId2(delta);
|
|
6523
|
-
if (runId)
|
|
6524
|
-
active.runIds.add(runId);
|
|
6878
|
+
if (page.nextBefore !== undefined) {
|
|
6879
|
+
state.nextBefore = page.nextBefore;
|
|
6525
6880
|
}
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
this.enqueue(sdkMessage);
|
|
6881
|
+
if (page.hasMore !== undefined) {
|
|
6882
|
+
state.hasMore = page.hasMore;
|
|
6529
6883
|
}
|
|
6530
|
-
|
|
6531
|
-
}
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
if (!status)
|
|
6535
|
-
return;
|
|
6536
|
-
const activeRunIds = loopStatusRunIds(message);
|
|
6537
|
-
const sdkMessage = {
|
|
6538
|
-
type: "loop_status",
|
|
6539
|
-
status,
|
|
6540
|
-
activeRunIds
|
|
6541
|
-
};
|
|
6542
|
-
this.enqueue(sdkMessage);
|
|
6543
|
-
const active = this.activeTurn;
|
|
6544
|
-
if (!active)
|
|
6545
|
-
return;
|
|
6546
|
-
for (const runId of activeRunIds)
|
|
6547
|
-
active.runIds.add(runId);
|
|
6548
|
-
const hadTurnEvidence = active.observedTurnEvidence || active.observedRequiresApprovalStop;
|
|
6549
|
-
if (!hadTurnEvidence)
|
|
6550
|
-
return;
|
|
6551
|
-
if (status === "WAITING_ON_APPROVAL") {
|
|
6552
|
-
this.completeActiveTurn({
|
|
6553
|
-
runtime: active.runtime,
|
|
6554
|
-
stopReason: "requires_approval",
|
|
6555
|
-
runIds: [...active.runIds]
|
|
6556
|
-
});
|
|
6884
|
+
return state;
|
|
6885
|
+
}
|
|
6886
|
+
close() {
|
|
6887
|
+
if (this.closed)
|
|
6557
6888
|
return;
|
|
6889
|
+
this.closed = true;
|
|
6890
|
+
this.removeMessageHandler?.();
|
|
6891
|
+
this.removeMessageHandler = null;
|
|
6892
|
+
this.turns.close();
|
|
6893
|
+
for (const cancel of [...this.deviceStatusRefreshCancels]) {
|
|
6894
|
+
cancel(new Error(`Session closed while waiting for ${this.label} device status`));
|
|
6558
6895
|
}
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6896
|
+
this.deviceStatusRefreshCancels.clear();
|
|
6897
|
+
this.deviceStatusListeners.clear();
|
|
6898
|
+
this.controller?.close();
|
|
6899
|
+
this.controller = null;
|
|
6900
|
+
this.onCoreClose();
|
|
6901
|
+
}
|
|
6902
|
+
get agentId() {
|
|
6903
|
+
return this._agentId;
|
|
6904
|
+
}
|
|
6905
|
+
get sessionId() {
|
|
6906
|
+
return this._sessionId;
|
|
6907
|
+
}
|
|
6908
|
+
get conversationId() {
|
|
6909
|
+
return this._conversationId;
|
|
6910
|
+
}
|
|
6911
|
+
async[Symbol.asyncDispose]() {
|
|
6912
|
+
this.close();
|
|
6913
|
+
}
|
|
6914
|
+
async changeDeviceState(updates) {
|
|
6915
|
+
if (!this.initialized) {
|
|
6916
|
+
await this.initialize();
|
|
6569
6917
|
}
|
|
6570
|
-
if (
|
|
6571
|
-
|
|
6572
|
-
runtime: active.runtime,
|
|
6573
|
-
stopReason: null,
|
|
6574
|
-
runIds: [...active.runIds]
|
|
6575
|
-
});
|
|
6918
|
+
if (!this.controller || !this.runtime) {
|
|
6919
|
+
throw new Error("Session is not initialized");
|
|
6576
6920
|
}
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
if (
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
const stopReason = streamDeltaStopReason2(delta) ?? null;
|
|
6585
|
-
if (stopReason === "requires_approval") {
|
|
6586
|
-
active.observedRequiresApprovalStop = true;
|
|
6587
|
-
return;
|
|
6588
|
-
}
|
|
6589
|
-
this.completeActiveTurn({
|
|
6590
|
-
runtime: active.runtime,
|
|
6591
|
-
stopReason,
|
|
6592
|
-
runIds: [...active.runIds]
|
|
6593
|
-
});
|
|
6594
|
-
return;
|
|
6921
|
+
const payload = {};
|
|
6922
|
+
if (updates.cwd !== undefined)
|
|
6923
|
+
payload.cwd = updates.cwd;
|
|
6924
|
+
if (updates.permissionMode !== undefined) {
|
|
6925
|
+
const mode = mapPermissionMode(updates.permissionMode);
|
|
6926
|
+
if (mode !== undefined)
|
|
6927
|
+
payload.mode = mode;
|
|
6595
6928
|
}
|
|
6596
|
-
if (
|
|
6597
|
-
|
|
6598
|
-
runtime: active.runtime,
|
|
6599
|
-
stopReason: sdkMessage.stopReason,
|
|
6600
|
-
runIds: [...active.runIds],
|
|
6601
|
-
success: false,
|
|
6602
|
-
detail: sdkMessage.errorDetail ?? sdkMessage.message,
|
|
6603
|
-
errorCode: sdkMessage.errorCode
|
|
6604
|
-
});
|
|
6929
|
+
if (Object.keys(payload).length === 0) {
|
|
6930
|
+
throw new Error("Invalid device state update. Expected cwd or permissionMode.");
|
|
6605
6931
|
}
|
|
6932
|
+
this.controller.send({
|
|
6933
|
+
type: "change_device_state",
|
|
6934
|
+
runtime: this.runtime,
|
|
6935
|
+
payload
|
|
6936
|
+
});
|
|
6606
6937
|
}
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
const uuid = typeof delta.id === "string" ? delta.id : `${this.label}-${++this.messageCounter}`;
|
|
6611
|
-
if (messageType === "assistant_message") {
|
|
6612
|
-
const content = extractTextFromContent(delta.content);
|
|
6613
|
-
if (!content)
|
|
6614
|
-
return null;
|
|
6615
|
-
if (this.activeTurn)
|
|
6616
|
-
this.activeTurn.assistantText += content;
|
|
6617
|
-
return { type: "assistant", content, uuid, runId };
|
|
6938
|
+
async updateToolset(toolsetPreference) {
|
|
6939
|
+
if (!this.initialized) {
|
|
6940
|
+
await this.initialize();
|
|
6618
6941
|
}
|
|
6619
|
-
if (
|
|
6620
|
-
|
|
6621
|
-
if (!content)
|
|
6622
|
-
return null;
|
|
6623
|
-
return { type: "reasoning", content, uuid, runId };
|
|
6942
|
+
if (!this.controller || !this.runtime) {
|
|
6943
|
+
throw new Error("Session is not initialized");
|
|
6624
6944
|
}
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6945
|
+
const response = await this.controller.request("update_toolset", {
|
|
6946
|
+
runtime: this.runtime,
|
|
6947
|
+
toolset_preference: toolsetPreference
|
|
6948
|
+
}, { predicate: (message) => message.type === "update_toolset_response" });
|
|
6949
|
+
ensureSuccess(response, "Failed to update toolset");
|
|
6950
|
+
}
|
|
6951
|
+
async afterRuntimeInitialized() {}
|
|
6952
|
+
async beforeTurn() {}
|
|
6953
|
+
onCoreClose() {}
|
|
6954
|
+
currentOptions() {
|
|
6955
|
+
return this.mode.options;
|
|
6956
|
+
}
|
|
6957
|
+
shouldEnableMemfs(options) {
|
|
6958
|
+
return false;
|
|
6959
|
+
}
|
|
6960
|
+
enableMemfsBody() {
|
|
6961
|
+
if (!this.runtime)
|
|
6962
|
+
return {};
|
|
6963
|
+
return { agent_id: this.runtime.agent_id };
|
|
6964
|
+
}
|
|
6965
|
+
setModel(model) {
|
|
6966
|
+
this._model = model;
|
|
6967
|
+
}
|
|
6968
|
+
async resolveUpdateModelPayload(input) {
|
|
6969
|
+
if (input.reasoningEffort === undefined) {
|
|
6970
|
+
return modelPayloadWithoutReasoning(input);
|
|
6971
|
+
}
|
|
6972
|
+
if (!this.controller) {
|
|
6973
|
+
throw new Error("Session is not initialized");
|
|
6974
|
+
}
|
|
6975
|
+
const catalog = await this.controller.listModels();
|
|
6976
|
+
const byId = new Map(catalog.entries.map((entry) => [entry.id, entry]));
|
|
6977
|
+
const aliases = catalog.byokProviderAliases;
|
|
6978
|
+
let baseEntry;
|
|
6979
|
+
let explicitHandle;
|
|
6980
|
+
let targetHandle;
|
|
6981
|
+
if (input.modelId !== undefined) {
|
|
6982
|
+
baseEntry = byId.get(input.modelId);
|
|
6983
|
+
explicitHandle = input.modelHandle;
|
|
6984
|
+
targetHandle = baseEntry?.handle ?? toBaseModelHandle(input.modelHandle, aliases);
|
|
6985
|
+
} else if (input.modelHandle !== undefined) {
|
|
6986
|
+
explicitHandle = input.modelHandle;
|
|
6987
|
+
targetHandle = toBaseModelHandle(input.modelHandle, aliases);
|
|
6988
|
+
} else if (input.model !== undefined) {
|
|
6989
|
+
baseEntry = byId.get(input.model);
|
|
6990
|
+
if (baseEntry) {
|
|
6991
|
+
targetHandle = baseEntry.handle;
|
|
6992
|
+
} else {
|
|
6993
|
+
explicitHandle = input.model;
|
|
6994
|
+
targetHandle = toBaseModelHandle(input.model, aliases);
|
|
6642
6995
|
}
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
type: "tool_call",
|
|
6647
|
-
toolCallId,
|
|
6648
|
-
toolName,
|
|
6649
|
-
toolInput: input,
|
|
6650
|
-
rawArguments: raw,
|
|
6651
|
-
uuid,
|
|
6652
|
-
runId
|
|
6653
|
-
};
|
|
6996
|
+
} else {
|
|
6997
|
+
explicitHandle = this._model || undefined;
|
|
6998
|
+
targetHandle = toBaseModelHandle(this._model || undefined, aliases);
|
|
6654
6999
|
}
|
|
6655
|
-
if (
|
|
6656
|
-
|
|
6657
|
-
const toolCallId = (typeof delta.tool_call_id === "string" ? delta.tool_call_id : undefined) ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
|
|
6658
|
-
if (!toolCallId)
|
|
6659
|
-
return null;
|
|
6660
|
-
const content = extractTextFromContent(delta.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
|
|
6661
|
-
const status = typeof delta.status === "string" ? delta.status : toolReturn.status;
|
|
6662
|
-
return {
|
|
6663
|
-
type: "tool_result",
|
|
6664
|
-
toolCallId,
|
|
6665
|
-
content,
|
|
6666
|
-
isError: status === "error",
|
|
6667
|
-
uuid,
|
|
6668
|
-
runId
|
|
6669
|
-
};
|
|
7000
|
+
if (!targetHandle) {
|
|
7001
|
+
throw new Error("reasoningEffort requires a current model or explicit model/modelId/modelHandle.");
|
|
6670
7002
|
}
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
const approvalConflict = isApprovalConflictSignal({
|
|
6675
|
-
detail,
|
|
6676
|
-
message: typeof delta.message === "string" ? delta.message : undefined,
|
|
6677
|
-
stopReason
|
|
6678
|
-
});
|
|
6679
|
-
return {
|
|
6680
|
-
type: "error",
|
|
6681
|
-
message: detail,
|
|
6682
|
-
errorCode: approvalConflict ? "approval_conflict" : toSdkErrorCode(stopReason),
|
|
6683
|
-
approvalConflict: approvalConflict || undefined,
|
|
6684
|
-
recoverable: approvalConflict ? true : false,
|
|
6685
|
-
errorDetail: detail,
|
|
6686
|
-
stopReason,
|
|
6687
|
-
runId
|
|
6688
|
-
};
|
|
7003
|
+
const candidates = catalog.entries.filter((entry) => entry.handle === targetHandle || entry.handle === explicitHandle);
|
|
7004
|
+
if (candidates.length === 0) {
|
|
7005
|
+
throw new Error(`reasoningEffort requires a model from listModels(); no catalog entry found for ${targetHandle}.`);
|
|
6689
7006
|
}
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
maxAttempts: typeof delta.max_attempts === "number" ? delta.max_attempts : 0,
|
|
6696
|
-
delayMs: typeof delta.delay_ms === "number" ? delta.delay_ms : 0,
|
|
6697
|
-
runId
|
|
6698
|
-
};
|
|
7007
|
+
const contextWindow = getContextWindow(baseEntry?.updateArgs) ?? getContextWindow(this._modelSettings);
|
|
7008
|
+
const scopedCandidates = sameContextCandidates(candidates, contextWindow);
|
|
7009
|
+
const matchingEntry = scopedCandidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort) ?? candidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort);
|
|
7010
|
+
if (!matchingEntry) {
|
|
7011
|
+
throw new Error(`No ${input.reasoningEffort} reasoning tier found for model ${targetHandle}.`);
|
|
6699
7012
|
}
|
|
6700
|
-
|
|
6701
|
-
|
|
7013
|
+
const payload = { model_id: matchingEntry.id };
|
|
7014
|
+
if (explicitHandle !== undefined) {
|
|
7015
|
+
payload.model_handle = explicitHandle;
|
|
6702
7016
|
}
|
|
6703
|
-
return
|
|
6704
|
-
type: "stream_event",
|
|
6705
|
-
event: delta,
|
|
6706
|
-
uuid
|
|
6707
|
-
};
|
|
6708
|
-
}
|
|
6709
|
-
resultFromTurn(turn, tracker) {
|
|
6710
|
-
const stopReason = turn.stopReason ?? (turn.success === false ? "error" : undefined);
|
|
6711
|
-
const approvalConflict = isApprovalConflictSignal({
|
|
6712
|
-
detail: turn.detail,
|
|
6713
|
-
stopReason
|
|
6714
|
-
});
|
|
6715
|
-
const success = turn.success !== undefined ? turn.success && !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "") : !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "");
|
|
6716
|
-
const errorCode = approvalConflict ? "approval_conflict" : turn.errorCode ?? toSdkErrorCode(stopReason);
|
|
6717
|
-
return {
|
|
6718
|
-
type: "result",
|
|
6719
|
-
success,
|
|
6720
|
-
result: success ? tracker?.assistantText || undefined : undefined,
|
|
6721
|
-
error: success ? undefined : errorCode ?? stopReason ?? "error",
|
|
6722
|
-
errorCode: success ? undefined : errorCode ?? "error",
|
|
6723
|
-
approvalConflict: approvalConflict || undefined,
|
|
6724
|
-
recoverable: approvalConflict ? true : success ? undefined : false,
|
|
6725
|
-
errorDetail: success ? undefined : turn.detail,
|
|
6726
|
-
stopReason,
|
|
6727
|
-
durationMs: Date.now() - (tracker?.startedAt || this.activeTurnStartedAt),
|
|
6728
|
-
conversationId: turn.runtime.conversation_id,
|
|
6729
|
-
runIds: turn.runIds.length > 0 ? turn.runIds : undefined
|
|
6730
|
-
};
|
|
7017
|
+
return payload;
|
|
6731
7018
|
}
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
if (resolver) {
|
|
6735
|
-
resolver(message);
|
|
7019
|
+
async applyPostInitializeOptions() {
|
|
7020
|
+
if (!this.controller || !this.runtime)
|
|
6736
7021
|
return;
|
|
7022
|
+
const options = this.currentOptions();
|
|
7023
|
+
if (this.shouldEnableMemfs(options)) {
|
|
7024
|
+
const response = await this.controller.request("enable_memfs", this.enableMemfsBody(), {
|
|
7025
|
+
predicate: (message) => message.type === "enable_memfs_response",
|
|
7026
|
+
timeoutMs: 180000
|
|
7027
|
+
});
|
|
7028
|
+
ensureSuccess(response, "Failed to enable memfs");
|
|
7029
|
+
}
|
|
7030
|
+
const dreamingSettings = resolveDreamingSettings(options.dreaming);
|
|
7031
|
+
if (dreamingSettings) {
|
|
7032
|
+
const response = await this.controller.request("set_reflection_settings", {
|
|
7033
|
+
runtime: this.runtime,
|
|
7034
|
+
settings: dreamingSettings,
|
|
7035
|
+
scope: "both"
|
|
7036
|
+
}, { predicate: (message) => message.type === "set_reflection_settings_response" });
|
|
7037
|
+
ensureSuccess(response, "Failed to update dreaming settings");
|
|
7038
|
+
}
|
|
7039
|
+
if (this.mode.kind !== "session")
|
|
7040
|
+
return;
|
|
7041
|
+
if (this.mode.options.model !== undefined || this.mode.options.reasoningEffort !== undefined) {
|
|
7042
|
+
await this.applyModelUpdate({
|
|
7043
|
+
...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
|
|
7044
|
+
...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
|
|
7045
|
+
});
|
|
6737
7046
|
}
|
|
6738
|
-
this.streamQueue.push(message);
|
|
6739
|
-
}
|
|
6740
|
-
nextMessage() {
|
|
6741
|
-
const next = this.streamQueue.shift();
|
|
6742
|
-
if (next)
|
|
6743
|
-
return Promise.resolve(next);
|
|
6744
|
-
if (this.closed)
|
|
6745
|
-
return Promise.resolve(null);
|
|
6746
|
-
return new Promise((resolve) => {
|
|
6747
|
-
this.streamResolvers.push(resolve);
|
|
6748
|
-
});
|
|
6749
7047
|
}
|
|
6750
|
-
|
|
6751
|
-
for (const
|
|
6752
|
-
|
|
7048
|
+
emitDeviceStatus(status) {
|
|
7049
|
+
for (const listener of [...this.deviceStatusListeners]) {
|
|
7050
|
+
try {
|
|
7051
|
+
listener(status);
|
|
7052
|
+
} catch {}
|
|
6753
7053
|
}
|
|
6754
7054
|
}
|
|
6755
7055
|
}
|
|
@@ -7243,13 +7543,15 @@ class AppServerSession extends RemoteClientSessionCore {
|
|
|
7243
7543
|
return options.memfs !== false;
|
|
7244
7544
|
}
|
|
7245
7545
|
async initializeRuntimeController() {
|
|
7546
|
+
await this.remoteOptions.beforeConnect?.();
|
|
7246
7547
|
const url = await this.resolveAppServerUrl();
|
|
7247
|
-
|
|
7548
|
+
await releaseAppServerManagementConnections(url);
|
|
7549
|
+
const client = applyUniqueRequestIds(createAppServerClient({
|
|
7248
7550
|
url,
|
|
7249
7551
|
...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
|
|
7250
7552
|
...this.remoteOptions.WebSocket ? { WebSocket: this.remoteOptions.WebSocket } : {},
|
|
7251
7553
|
...this.remoteOptions.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.remoteOptions.requestTimeoutMs } : {}
|
|
7252
|
-
});
|
|
7554
|
+
}));
|
|
7253
7555
|
this.removeControlRequestHandler = registerAppServerControlRequestHandler({
|
|
7254
7556
|
client,
|
|
7255
7557
|
getRuntime: () => this.runtime,
|
|
@@ -7648,13 +7950,58 @@ class RemoteEnvironmentClient {
|
|
|
7648
7950
|
}
|
|
7649
7951
|
}
|
|
7650
7952
|
|
|
7953
|
+
// src/cloud-sandbox.ts
|
|
7954
|
+
var MIN_TTL_MINUTES = 1;
|
|
7955
|
+
var MAX_TTL_MINUTES = 60;
|
|
7956
|
+
var MAX_GITHUB_REPOSITORIES = 10;
|
|
7957
|
+
var GITHUB_OWNER_PATTERN = /^[A-Za-z0-9-]+$/;
|
|
7958
|
+
var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7959
|
+
function validatePositiveInteger(value, name) {
|
|
7960
|
+
if (value !== undefined && (!Number.isInteger(value) || value <= 0)) {
|
|
7961
|
+
throw new Error(`Invalid ${name}. Expected a positive integer.`);
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
function validateCloudSandboxOptions(options, name) {
|
|
7965
|
+
if (options === undefined)
|
|
7966
|
+
return;
|
|
7967
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
7968
|
+
throw new Error(`Invalid ${name}. Expected an object.`);
|
|
7969
|
+
}
|
|
7970
|
+
if (options.ttlMinutes !== undefined && (!Number.isInteger(options.ttlMinutes) || options.ttlMinutes < MIN_TTL_MINUTES || options.ttlMinutes > MAX_TTL_MINUTES)) {
|
|
7971
|
+
throw new Error(`Invalid ${name}.ttlMinutes. Expected an integer between ${MIN_TTL_MINUTES} and ${MAX_TTL_MINUTES}.`);
|
|
7972
|
+
}
|
|
7973
|
+
validatePositiveInteger(options.readyTimeoutMs, `${name}.readyTimeoutMs`);
|
|
7974
|
+
validatePositiveInteger(options.readyPollIntervalMs, `${name}.readyPollIntervalMs`);
|
|
7975
|
+
validatePositiveInteger(options.refreshIntervalMs, `${name}.refreshIntervalMs`);
|
|
7976
|
+
if (options.githubRepositories !== undefined) {
|
|
7977
|
+
if (!Array.isArray(options.githubRepositories)) {
|
|
7978
|
+
throw new Error(`Invalid ${name}.githubRepositories. Expected an array.`);
|
|
7979
|
+
}
|
|
7980
|
+
if (options.githubRepositories.length > MAX_GITHUB_REPOSITORIES) {
|
|
7981
|
+
throw new Error(`Invalid ${name}.githubRepositories. Expected at most ${MAX_GITHUB_REPOSITORIES} repositories.`);
|
|
7982
|
+
}
|
|
7983
|
+
for (const [index, repository] of options.githubRepositories.entries()) {
|
|
7984
|
+
if (repository === null || typeof repository !== "object" || Array.isArray(repository)) {
|
|
7985
|
+
throw new Error(`Invalid ${name}.githubRepositories[${index}]. Expected an object.`);
|
|
7986
|
+
}
|
|
7987
|
+
if (typeof repository.owner !== "string" || !GITHUB_OWNER_PATTERN.test(repository.owner)) {
|
|
7988
|
+
throw new Error(`Invalid ${name}.githubRepositories[${index}].owner.`);
|
|
7989
|
+
}
|
|
7990
|
+
if (typeof repository.repo !== "string" || !GITHUB_REPOSITORY_PATTERN.test(repository.repo)) {
|
|
7991
|
+
throw new Error(`Invalid ${name}.githubRepositories[${index}].repo.`);
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
}
|
|
7995
|
+
if (options.terminateOnClose !== undefined && typeof options.terminateOnClose !== "boolean") {
|
|
7996
|
+
throw new Error(`Invalid ${name}.terminateOnClose. Expected a boolean.`);
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
|
|
7651
8000
|
// src/cloud-session.ts
|
|
7652
8001
|
var DEFAULT_CLOUD_API_BASE_URL3 = "https://api.letta.com";
|
|
7653
8002
|
var DEFAULT_TURN_TIMEOUT_MS = 120000;
|
|
7654
8003
|
var DEFAULT_PING_INTERVAL_MS = 30000;
|
|
7655
8004
|
var DEFAULT_SANDBOX_TTL_MINUTES = 5;
|
|
7656
|
-
var MIN_SANDBOX_TTL_MINUTES = 1;
|
|
7657
|
-
var MAX_SANDBOX_TTL_MINUTES = 60;
|
|
7658
8005
|
var DEFAULT_SANDBOX_READY_TIMEOUT_MS = 120000;
|
|
7659
8006
|
var DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS = 1000;
|
|
7660
8007
|
var DEFAULT_REPOSITORY_ATTACH_TIMEOUT_MS = 1e4;
|
|
@@ -7765,34 +8112,13 @@ function assertOkResponse2(response, body, action, url) {
|
|
|
7765
8112
|
throw new Error(parts.join(" — "));
|
|
7766
8113
|
}
|
|
7767
8114
|
}
|
|
7768
|
-
function
|
|
8115
|
+
function validatePositiveInteger2(value, name) {
|
|
7769
8116
|
if (value !== undefined && (!Number.isInteger(value) || value <= 0)) {
|
|
7770
8117
|
throw new Error(`Invalid ${name}. Expected a positive integer.`);
|
|
7771
8118
|
}
|
|
7772
8119
|
}
|
|
7773
|
-
function validateIntegerRange(value, name, min, max) {
|
|
7774
|
-
if (value === undefined)
|
|
7775
|
-
return;
|
|
7776
|
-
if (!Number.isInteger(value) || value < min || value > max) {
|
|
7777
|
-
throw new Error(`Invalid ${name}. Expected an integer between ${min} and ${max}.`);
|
|
7778
|
-
}
|
|
7779
|
-
}
|
|
7780
|
-
function validateCloudSandboxOptions(options, name) {
|
|
7781
|
-
if (options === undefined)
|
|
7782
|
-
return;
|
|
7783
|
-
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
7784
|
-
throw new Error(`Invalid ${name}. Expected an object.`);
|
|
7785
|
-
}
|
|
7786
|
-
validateIntegerRange(options.ttlMinutes, `${name}.ttlMinutes`, MIN_SANDBOX_TTL_MINUTES, MAX_SANDBOX_TTL_MINUTES);
|
|
7787
|
-
validatePositiveInteger(options.readyTimeoutMs, `${name}.readyTimeoutMs`);
|
|
7788
|
-
validatePositiveInteger(options.readyPollIntervalMs, `${name}.readyPollIntervalMs`);
|
|
7789
|
-
validatePositiveInteger(options.refreshIntervalMs, `${name}.refreshIntervalMs`);
|
|
7790
|
-
if (options.terminateOnClose !== undefined && typeof options.terminateOnClose !== "boolean") {
|
|
7791
|
-
throw new Error(`Invalid ${name}.terminateOnClose. Expected a boolean.`);
|
|
7792
|
-
}
|
|
7793
|
-
}
|
|
7794
8120
|
function validateCloudClientOptions(options) {
|
|
7795
|
-
|
|
8121
|
+
validatePositiveInteger2(options.requestTimeoutMs, "requestTimeoutMs");
|
|
7796
8122
|
validateCloudSandboxOptions(options.sandbox, "sandbox");
|
|
7797
8123
|
if (options.environment !== undefined && options.sandbox !== undefined) {
|
|
7798
8124
|
throw new Error("Letta Cloud sessions cannot specify both environment and sandbox options.");
|
|
@@ -8120,14 +8446,14 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8120
8446
|
apiKey,
|
|
8121
8447
|
authMode: this.cloudOptions.webSocketAuth ?? "header"
|
|
8122
8448
|
});
|
|
8123
|
-
const client = createAppServerClient({
|
|
8449
|
+
const client = applyUniqueRequestIds(createAppServerClient({
|
|
8124
8450
|
url,
|
|
8125
8451
|
WebSocket: createCloudStatusWebSocketConstructor({
|
|
8126
8452
|
cloudOptions: this.cloudOptions,
|
|
8127
8453
|
runtime: resolved.runtime
|
|
8128
8454
|
}),
|
|
8129
8455
|
requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
|
|
8130
|
-
});
|
|
8456
|
+
}));
|
|
8131
8457
|
this.removeControlRequestHandler = registerAppServerControlRequestHandler({
|
|
8132
8458
|
client,
|
|
8133
8459
|
getRuntime: () => this.runtime,
|
|
@@ -8395,10 +8721,15 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8395
8721
|
async createManagedSandbox(agentId, conversationId) {
|
|
8396
8722
|
const fetchImpl = getFetch3(this.cloudOptions.fetch);
|
|
8397
8723
|
const baseUrl = normalizeCloudApiBaseUrl2(this.cloudOptions.apiBaseUrl);
|
|
8724
|
+
const sandboxOptions = this.resolvedSandboxOptions();
|
|
8725
|
+
const githubRepositories = sandboxOptions.githubRepositories;
|
|
8398
8726
|
const response = await fetchImpl(`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {
|
|
8399
8727
|
method: "POST",
|
|
8400
8728
|
headers: cloudHeaders2(this.cloudOptions),
|
|
8401
|
-
body: JSON.stringify(
|
|
8729
|
+
body: JSON.stringify({
|
|
8730
|
+
...conversationId ? { conversationId } : {},
|
|
8731
|
+
...githubRepositories && githubRepositories.length > 0 ? { githubRepositories } : {}
|
|
8732
|
+
})
|
|
8402
8733
|
});
|
|
8403
8734
|
const body = await parseJsonResponse3(response);
|
|
8404
8735
|
assertOkResponse2(response, body, "Cloud create managed sandbox", `${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sandboxes`);
|
|
@@ -8409,7 +8740,6 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8409
8740
|
if (responseConversationId !== null && responseConversationId !== conversationId) {
|
|
8410
8741
|
throw new Error(`Cloud managed sandbox response conversation mismatch: expected ${conversationId ?? "none"}, got ${responseConversationId}.`);
|
|
8411
8742
|
}
|
|
8412
|
-
const sandboxOptions = this.resolvedSandboxOptions();
|
|
8413
8743
|
const ttlMinutes = sandboxOptions.ttlMinutes ?? DEFAULT_SANDBOX_TTL_MINUTES;
|
|
8414
8744
|
const readyTimeoutMs = sandboxOptions.readyTimeoutMs ?? DEFAULT_SANDBOX_READY_TIMEOUT_MS;
|
|
8415
8745
|
const readyPollIntervalMs = sandboxOptions.readyPollIntervalMs ?? DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS;
|
|
@@ -8865,6 +9195,10 @@ class LettaAgentClientBase {
|
|
|
8865
9195
|
if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
|
|
8866
9196
|
throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
|
|
8867
9197
|
}
|
|
9198
|
+
const idleLingerMs = localOptions.appServer?.idleLingerMs;
|
|
9199
|
+
if (idleLingerMs !== undefined && (!Number.isInteger(idleLingerMs) || idleLingerMs < 0)) {
|
|
9200
|
+
throw new Error("Invalid appServer.idleLingerMs. Expected a non-negative integer.");
|
|
9201
|
+
}
|
|
8868
9202
|
const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
|
|
8869
9203
|
if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
|
|
8870
9204
|
throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
|
|
@@ -8877,6 +9211,9 @@ class LettaAgentClientBase {
|
|
|
8877
9211
|
if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
|
|
8878
9212
|
throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
|
|
8879
9213
|
}
|
|
9214
|
+
if (options.idleLingerMs !== undefined && (!Number.isInteger(options.idleLingerMs) || options.idleLingerMs < 0)) {
|
|
9215
|
+
throw new Error("Invalid idleLingerMs. Expected a non-negative integer.");
|
|
9216
|
+
}
|
|
8880
9217
|
}
|
|
8881
9218
|
if (this.backend === "cloud") {
|
|
8882
9219
|
validateCloudClientOptions(options);
|
|
@@ -8891,7 +9228,7 @@ class LettaAgentClientBase {
|
|
|
8891
9228
|
}
|
|
8892
9229
|
validateCreateAgentOptions(options);
|
|
8893
9230
|
if (this.backend === "remote") {
|
|
8894
|
-
const session = new AppServerSession(this.
|
|
9231
|
+
const session = new AppServerSession(this.appServerSessionOptions(), {
|
|
8895
9232
|
kind: "create-agent",
|
|
8896
9233
|
options
|
|
8897
9234
|
});
|
|
@@ -8914,7 +9251,7 @@ class LettaAgentClientBase {
|
|
|
8914
9251
|
if (!agentId) {
|
|
8915
9252
|
throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
|
|
8916
9253
|
}
|
|
8917
|
-
return new AppServerSession(this.
|
|
9254
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
8918
9255
|
kind: "session",
|
|
8919
9256
|
agentId,
|
|
8920
9257
|
newConversation: true,
|
|
@@ -8940,13 +9277,13 @@ class LettaAgentClientBase {
|
|
|
8940
9277
|
validateCreateSessionOptions(sessionOptions);
|
|
8941
9278
|
if (this.backend === "remote") {
|
|
8942
9279
|
if (looksLikeConversationId(id)) {
|
|
8943
|
-
return new AppServerSession(this.
|
|
9280
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
8944
9281
|
kind: "session",
|
|
8945
9282
|
conversationId: id,
|
|
8946
9283
|
options
|
|
8947
9284
|
});
|
|
8948
9285
|
}
|
|
8949
|
-
return new AppServerSession(this.
|
|
9286
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
8950
9287
|
kind: "session",
|
|
8951
9288
|
agentId: id,
|
|
8952
9289
|
defaultConversation: true,
|
|
@@ -9045,6 +9382,12 @@ class LettaAgentClientBase {
|
|
|
9045
9382
|
}
|
|
9046
9383
|
return this.options;
|
|
9047
9384
|
}
|
|
9385
|
+
appServerSessionOptions() {
|
|
9386
|
+
return {
|
|
9387
|
+
...this.remoteOptions(),
|
|
9388
|
+
beforeConnect: () => this.releaseIdleManagementConnection()
|
|
9389
|
+
};
|
|
9390
|
+
}
|
|
9048
9391
|
getRepositoriesClient() {
|
|
9049
9392
|
if (this.backend !== "cloud") {
|
|
9050
9393
|
throw new Error('client.repositories is only available with backend: "cloud".');
|
|
@@ -9052,6 +9395,9 @@ class LettaAgentClientBase {
|
|
|
9052
9395
|
this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
|
|
9053
9396
|
return this.repositoriesClient;
|
|
9054
9397
|
}
|
|
9398
|
+
async releaseIdleManagementConnection() {
|
|
9399
|
+
await this.managementTransport?.releaseIdleConnection?.();
|
|
9400
|
+
}
|
|
9055
9401
|
getManagementTransport() {
|
|
9056
9402
|
if (this.managementTransport)
|
|
9057
9403
|
return this.managementTransport;
|
|
@@ -9201,7 +9547,7 @@ ${output.trim()}` : ""}`));
|
|
|
9201
9547
|
}
|
|
9202
9548
|
|
|
9203
9549
|
// src/local-app-server-session.ts
|
|
9204
|
-
function createLocalAppServerSession(options, mode) {
|
|
9550
|
+
function createLocalAppServerSession(options, mode, beforeConnect) {
|
|
9205
9551
|
const appServer = options ?? {};
|
|
9206
9552
|
const sessionOptions = {
|
|
9207
9553
|
...appServer.url !== undefined ? { url: appServer.url } : {
|
|
@@ -9214,7 +9560,8 @@ function createLocalAppServerSession(options, mode) {
|
|
|
9214
9560
|
},
|
|
9215
9561
|
...appServer.WebSocket !== undefined ? { WebSocket: appServer.WebSocket } : {},
|
|
9216
9562
|
...appServer.requestTimeoutMs !== undefined ? { requestTimeoutMs: appServer.requestTimeoutMs } : {},
|
|
9217
|
-
...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {}
|
|
9563
|
+
...appServer.pinGlobalAgent !== undefined ? { pinGlobalAgent: appServer.pinGlobalAgent } : {},
|
|
9564
|
+
...beforeConnect ? { beforeConnect } : {}
|
|
9218
9565
|
};
|
|
9219
9566
|
return new AppServerSession(sessionOptions, mode);
|
|
9220
9567
|
}
|
|
@@ -9614,6 +9961,8 @@ class Session {
|
|
|
9614
9961
|
_sessionId = null;
|
|
9615
9962
|
_conversationId = null;
|
|
9616
9963
|
initialized = false;
|
|
9964
|
+
closed = false;
|
|
9965
|
+
initializePromise = null;
|
|
9617
9966
|
externalTools = new Map;
|
|
9618
9967
|
streamQueue = [];
|
|
9619
9968
|
streamResolvers = [];
|
|
@@ -9636,9 +9985,28 @@ class Session {
|
|
|
9636
9985
|
}
|
|
9637
9986
|
}
|
|
9638
9987
|
async initialize() {
|
|
9988
|
+
if (this.closed) {
|
|
9989
|
+
throw new Error("Session is closed");
|
|
9990
|
+
}
|
|
9991
|
+
if (this.initializePromise) {
|
|
9992
|
+
return this.initializePromise;
|
|
9993
|
+
}
|
|
9639
9994
|
if (this.initialized) {
|
|
9640
9995
|
throw new Error("Session already initialized");
|
|
9641
9996
|
}
|
|
9997
|
+
const attempt = this.performInitialize();
|
|
9998
|
+
const memo = attempt.catch((error) => {
|
|
9999
|
+
this.cleanupFailedInitialize();
|
|
10000
|
+
throw error;
|
|
10001
|
+
}).finally(() => {
|
|
10002
|
+
if (this.initializePromise === memo) {
|
|
10003
|
+
this.initializePromise = null;
|
|
10004
|
+
}
|
|
10005
|
+
});
|
|
10006
|
+
this.initializePromise = memo;
|
|
10007
|
+
return memo;
|
|
10008
|
+
}
|
|
10009
|
+
async performInitialize() {
|
|
9642
10010
|
sessionLog("init", "connecting transport...");
|
|
9643
10011
|
await this.transport.connect();
|
|
9644
10012
|
sessionLog("init", "transport connected, sending initialize request");
|
|
@@ -9663,11 +10031,14 @@ class Session {
|
|
|
9663
10031
|
this._agentId = initMsg.agent_id;
|
|
9664
10032
|
this._sessionId = initMsg.session_id;
|
|
9665
10033
|
this._conversationId = initMsg.conversation_id;
|
|
9666
|
-
this.initialized = true;
|
|
9667
|
-
this.startBackgroundPump();
|
|
9668
10034
|
if (this.externalTools.size > 0) {
|
|
9669
10035
|
await this.registerExternalTools();
|
|
9670
10036
|
}
|
|
10037
|
+
if (this.closed) {
|
|
10038
|
+
throw new Error("Session is closed");
|
|
10039
|
+
}
|
|
10040
|
+
this.initialized = true;
|
|
10041
|
+
this.startBackgroundPump();
|
|
9671
10042
|
const allTools = [
|
|
9672
10043
|
...initMsg.tools,
|
|
9673
10044
|
...Array.from(this.externalTools.keys())
|
|
@@ -9698,6 +10069,16 @@ ${stderr}` : "";
|
|
|
9698
10069
|
sessionLog("init", `ERROR: transport closed before init message received${detail}`);
|
|
9699
10070
|
throw new Error(`Failed to initialize session - no init message received${detail}`);
|
|
9700
10071
|
}
|
|
10072
|
+
cleanupFailedInitialize() {
|
|
10073
|
+
this.transport.close();
|
|
10074
|
+
this.closed = true;
|
|
10075
|
+
this.initialized = false;
|
|
10076
|
+
this._agentId = null;
|
|
10077
|
+
this._sessionId = null;
|
|
10078
|
+
this._conversationId = null;
|
|
10079
|
+
this.pumpClosed = true;
|
|
10080
|
+
this.resolveAllStreamWaiters(null);
|
|
10081
|
+
}
|
|
9701
10082
|
async send(message) {
|
|
9702
10083
|
if (!this.initialized) {
|
|
9703
10084
|
sessionLog("send", "auto-initializing (not yet initialized)");
|
|
@@ -10374,6 +10755,9 @@ ${stderr}` : "";
|
|
|
10374
10755
|
throw new Error("updateToolset() is not supported by this session. Use a Cloud, Remote, or local agent session.");
|
|
10375
10756
|
}
|
|
10376
10757
|
close() {
|
|
10758
|
+
if (this.closed)
|
|
10759
|
+
return;
|
|
10760
|
+
this.closed = true;
|
|
10377
10761
|
sessionLog("close", `closing session (agent=${this._agentId}, conversation=${this._conversationId})`);
|
|
10378
10762
|
this.transport.close();
|
|
10379
10763
|
this.pumpClosed = true;
|
|
@@ -10571,7 +10955,8 @@ class LettaAgentClient extends LettaAgentClientBase {
|
|
|
10571
10955
|
})
|
|
10572
10956
|
},
|
|
10573
10957
|
...localOptions?.WebSocket !== undefined ? { WebSocket: localOptions.WebSocket } : {},
|
|
10574
|
-
...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {}
|
|
10958
|
+
...localOptions?.requestTimeoutMs !== undefined ? { requestTimeoutMs: localOptions.requestTimeoutMs } : {},
|
|
10959
|
+
...localOptions?.idleLingerMs !== undefined ? { idleLingerMs: localOptions.idleLingerMs } : {}
|
|
10575
10960
|
});
|
|
10576
10961
|
}
|
|
10577
10962
|
async createLocalAgent(options) {
|
|
@@ -10580,7 +10965,7 @@ class LettaAgentClient extends LettaAgentClientBase {
|
|
|
10580
10965
|
const session2 = createLocalAppServerSession(localOptions.appServer, {
|
|
10581
10966
|
kind: "create-agent",
|
|
10582
10967
|
options
|
|
10583
|
-
});
|
|
10968
|
+
}, () => this.releaseIdleManagementConnection());
|
|
10584
10969
|
const initMsg2 = await session2.initialize();
|
|
10585
10970
|
session2.close();
|
|
10586
10971
|
return initMsg2.agentId;
|
|
@@ -10598,7 +10983,7 @@ class LettaAgentClient extends LettaAgentClientBase {
|
|
|
10598
10983
|
agentId,
|
|
10599
10984
|
newConversation: true,
|
|
10600
10985
|
options
|
|
10601
|
-
});
|
|
10986
|
+
}, () => this.releaseIdleManagementConnection());
|
|
10602
10987
|
}
|
|
10603
10988
|
if (agentId) {
|
|
10604
10989
|
return new Session({ ...sessionOptions, agentId, newConversation: true });
|
|
@@ -10613,14 +10998,14 @@ class LettaAgentClient extends LettaAgentClientBase {
|
|
|
10613
10998
|
kind: "session",
|
|
10614
10999
|
conversationId: id,
|
|
10615
11000
|
options
|
|
10616
|
-
});
|
|
11001
|
+
}, () => this.releaseIdleManagementConnection());
|
|
10617
11002
|
}
|
|
10618
11003
|
return createLocalAppServerSession(localOptions.appServer, {
|
|
10619
11004
|
kind: "session",
|
|
10620
11005
|
agentId: id,
|
|
10621
11006
|
defaultConversation: true,
|
|
10622
11007
|
options
|
|
10623
|
-
});
|
|
11008
|
+
}, () => this.releaseIdleManagementConnection());
|
|
10624
11009
|
}
|
|
10625
11010
|
if (looksLikeConversationId2(id)) {
|
|
10626
11011
|
return new Session({ ...sessionOptions, conversationId: id });
|
|
@@ -10888,4 +11273,4 @@ export {
|
|
|
10888
11273
|
CloudManagedSandboxExpiredError
|
|
10889
11274
|
};
|
|
10890
11275
|
|
|
10891
|
-
//# debugId=
|
|
11276
|
+
//# debugId=01C64A8CE3FD2BF964756E2164756E21
|