@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/client-entry.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Generated bundle. Canonical modules are published under src/ and maintained in the letta-agent-sdk repository.
|
|
2
|
+
|
|
1
3
|
// src/repositories.ts
|
|
2
4
|
var DEFAULT_CLOUD_API_BASE_URL = "https://api.letta.com";
|
|
3
5
|
function getDefaultApiKey() {
|
|
@@ -682,7 +684,56 @@ function createAppServerClient(options) {
|
|
|
682
684
|
return new AppServerClient(options);
|
|
683
685
|
}
|
|
684
686
|
|
|
687
|
+
// src/request-ids.ts
|
|
688
|
+
var processRequestCounter = 0;
|
|
689
|
+
function createRequestIdGenerator() {
|
|
690
|
+
const nonce = Math.random().toString(36).slice(2, 10);
|
|
691
|
+
return (prefix = "req") => `${prefix}-${nonce}-${++processRequestCounter}`;
|
|
692
|
+
}
|
|
693
|
+
function applyUniqueRequestIds(client) {
|
|
694
|
+
client.nextRequestId = createRequestIdGenerator();
|
|
695
|
+
return client;
|
|
696
|
+
}
|
|
697
|
+
|
|
685
698
|
// src/app-server-management.ts
|
|
699
|
+
var DEFAULT_IDLE_LINGER_MS = 250;
|
|
700
|
+
var registeredTransportsByUrl = new Map;
|
|
701
|
+
function appServerUrlKey(value) {
|
|
702
|
+
try {
|
|
703
|
+
const url = new URL(value);
|
|
704
|
+
if (url.protocol === "http:")
|
|
705
|
+
url.protocol = "ws:";
|
|
706
|
+
if (url.protocol === "https:")
|
|
707
|
+
url.protocol = "wss:";
|
|
708
|
+
url.hash = "";
|
|
709
|
+
url.searchParams.delete("channel");
|
|
710
|
+
return url.toString();
|
|
711
|
+
} catch {
|
|
712
|
+
return value;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function registerManagementTransport(url, transport) {
|
|
716
|
+
const key = appServerUrlKey(url);
|
|
717
|
+
const registration = { transport };
|
|
718
|
+
const transports = registeredTransportsByUrl.get(key) ?? new Set;
|
|
719
|
+
transports.add(registration);
|
|
720
|
+
registeredTransportsByUrl.set(key, transports);
|
|
721
|
+
return () => {
|
|
722
|
+
transports.delete(registration);
|
|
723
|
+
if (transports.size === 0) {
|
|
724
|
+
registeredTransportsByUrl.delete(key);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
async function releaseAppServerManagementConnections(url) {
|
|
729
|
+
const key = appServerUrlKey(url);
|
|
730
|
+
while (true) {
|
|
731
|
+
const transports = [...registeredTransportsByUrl.get(key) ?? []];
|
|
732
|
+
if (transports.length === 0)
|
|
733
|
+
return;
|
|
734
|
+
await Promise.all(transports.map(({ transport }) => transport.releaseIdleConnection()));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
686
737
|
function ensureResponse(response, value, fallback) {
|
|
687
738
|
if (!response.success || value == null) {
|
|
688
739
|
throw new Error(response.error ?? fallback);
|
|
@@ -718,8 +769,16 @@ function stringRecord(value) {
|
|
|
718
769
|
|
|
719
770
|
class AppServerManagementTransport {
|
|
720
771
|
options;
|
|
772
|
+
connectionPromise = null;
|
|
773
|
+
releasePromise = null;
|
|
774
|
+
closingConnections = new Set;
|
|
775
|
+
inFlightRequests = 0;
|
|
776
|
+
idleWaiters = new Set;
|
|
777
|
+
idleTimer = null;
|
|
778
|
+
idleLingerMs;
|
|
721
779
|
constructor(options) {
|
|
722
780
|
this.options = options;
|
|
781
|
+
this.idleLingerMs = options.idleLingerMs ?? DEFAULT_IDLE_LINGER_MS;
|
|
723
782
|
}
|
|
724
783
|
async listAgents(query) {
|
|
725
784
|
const response = await this.request("agent_list", { query }, "agent_list_response");
|
|
@@ -786,38 +845,206 @@ class AppServerManagementTransport {
|
|
|
786
845
|
}
|
|
787
846
|
return { messages: response.messages };
|
|
788
847
|
}
|
|
848
|
+
releaseIdleConnection() {
|
|
849
|
+
this.clearIdleTimer();
|
|
850
|
+
if (this.releasePromise)
|
|
851
|
+
return this.releasePromise;
|
|
852
|
+
const release = this.releaseConnectionWhenIdle();
|
|
853
|
+
this.releasePromise = release;
|
|
854
|
+
release.then(() => {
|
|
855
|
+
if (this.releasePromise === release) {
|
|
856
|
+
this.releasePromise = null;
|
|
857
|
+
}
|
|
858
|
+
}, () => {
|
|
859
|
+
if (this.releasePromise === release) {
|
|
860
|
+
this.releasePromise = null;
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
return release;
|
|
864
|
+
}
|
|
789
865
|
async request(type, body, responseType) {
|
|
790
|
-
|
|
866
|
+
if (this.releasePromise) {
|
|
867
|
+
await this.releasePromise;
|
|
868
|
+
}
|
|
869
|
+
if (this.closingConnections.size > 0) {
|
|
870
|
+
await Promise.all([...this.closingConnections]);
|
|
871
|
+
}
|
|
872
|
+
this.clearIdleTimer();
|
|
873
|
+
this.inFlightRequests += 1;
|
|
874
|
+
try {
|
|
875
|
+
const { client } = await this.acquireConnection();
|
|
876
|
+
const command = {
|
|
877
|
+
type,
|
|
878
|
+
request_id: client.nextRequestId(type),
|
|
879
|
+
...body
|
|
880
|
+
};
|
|
881
|
+
const response = await client.request(command, {
|
|
882
|
+
predicate: (message) => message.type === responseType
|
|
883
|
+
});
|
|
884
|
+
return response;
|
|
885
|
+
} finally {
|
|
886
|
+
this.inFlightRequests -= 1;
|
|
887
|
+
if (this.inFlightRequests === 0) {
|
|
888
|
+
const waiters = [...this.idleWaiters];
|
|
889
|
+
this.idleWaiters.clear();
|
|
890
|
+
for (const resolve of waiters)
|
|
891
|
+
resolve();
|
|
892
|
+
if (!this.releasePromise) {
|
|
893
|
+
this.scheduleIdleRelease();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async releaseConnectionWhenIdle() {
|
|
899
|
+
if (this.inFlightRequests > 0) {
|
|
900
|
+
await new Promise((resolve) => {
|
|
901
|
+
this.idleWaiters.add(resolve);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
this.clearIdleTimer();
|
|
905
|
+
const promise = this.connectionPromise;
|
|
906
|
+
try {
|
|
907
|
+
if (promise) {
|
|
908
|
+
this.connectionPromise = null;
|
|
909
|
+
await this.trackClosingConnection(await promise);
|
|
910
|
+
}
|
|
911
|
+
if (this.closingConnections.size > 0) {
|
|
912
|
+
await Promise.all([...this.closingConnections]);
|
|
913
|
+
}
|
|
914
|
+
} catch {}
|
|
915
|
+
}
|
|
916
|
+
acquireConnection() {
|
|
917
|
+
if (this.connectionPromise)
|
|
918
|
+
return this.connectionPromise;
|
|
919
|
+
const promise = this.openConnection().then((connection) => {
|
|
920
|
+
connection.detachDisconnect = connection.client.onDisconnect(() => {
|
|
921
|
+
this.discardConnection(promise, connection);
|
|
922
|
+
});
|
|
923
|
+
return connection;
|
|
924
|
+
}, (error) => {
|
|
925
|
+
if (this.connectionPromise === promise) {
|
|
926
|
+
this.connectionPromise = null;
|
|
927
|
+
}
|
|
928
|
+
throw error;
|
|
929
|
+
});
|
|
930
|
+
this.connectionPromise = promise;
|
|
931
|
+
return promise;
|
|
932
|
+
}
|
|
933
|
+
async openConnection() {
|
|
934
|
+
const ownedConnection = this.options.url ? null : await this.options.connect?.() ?? null;
|
|
791
935
|
const url = this.options.url ?? ownedConnection?.url;
|
|
792
936
|
if (!url) {
|
|
793
937
|
throw new Error("App-server management requires a url or connect hook.");
|
|
794
938
|
}
|
|
939
|
+
const unregister = registerManagementTransport(url, this);
|
|
795
940
|
let client = null;
|
|
796
941
|
try {
|
|
797
|
-
client = createAppServerClient({
|
|
942
|
+
client = applyUniqueRequestIds(createAppServerClient({
|
|
798
943
|
url,
|
|
799
944
|
...this.options.authToken !== undefined ? { authToken: this.options.authToken } : {},
|
|
800
945
|
...this.options.WebSocket ? {
|
|
801
946
|
WebSocket: this.options.WebSocket
|
|
802
947
|
} : {},
|
|
803
948
|
...this.options.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.options.requestTimeoutMs } : {}
|
|
804
|
-
});
|
|
949
|
+
}));
|
|
805
950
|
await client.connect();
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
951
|
+
} catch (error) {
|
|
952
|
+
try {
|
|
953
|
+
if (client) {
|
|
954
|
+
const controlClosed = waitForSocketClose(client.control);
|
|
955
|
+
client.close();
|
|
956
|
+
ownedConnection?.close();
|
|
957
|
+
await controlClosed;
|
|
958
|
+
} else {
|
|
959
|
+
ownedConnection?.close();
|
|
960
|
+
}
|
|
961
|
+
} catch {} finally {
|
|
962
|
+
unregister();
|
|
963
|
+
}
|
|
964
|
+
throw error;
|
|
818
965
|
}
|
|
966
|
+
return {
|
|
967
|
+
client,
|
|
968
|
+
ownedConnection,
|
|
969
|
+
detachDisconnect: () => {},
|
|
970
|
+
unregister
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
discardConnection(promise, connection) {
|
|
974
|
+
if (this.connectionPromise === promise) {
|
|
975
|
+
this.connectionPromise = null;
|
|
976
|
+
this.clearIdleTimer();
|
|
977
|
+
}
|
|
978
|
+
this.trackClosingConnection(connection);
|
|
979
|
+
}
|
|
980
|
+
trackClosingConnection(connection) {
|
|
981
|
+
const closing = closeConnection(connection);
|
|
982
|
+
this.closingConnections.add(closing);
|
|
983
|
+
closing.then(() => this.closingConnections.delete(closing), () => this.closingConnections.delete(closing));
|
|
984
|
+
return closing;
|
|
985
|
+
}
|
|
986
|
+
scheduleIdleRelease() {
|
|
987
|
+
if (!this.connectionPromise)
|
|
988
|
+
return;
|
|
989
|
+
this.clearIdleTimer();
|
|
990
|
+
const timer = setTimeout(() => {
|
|
991
|
+
this.idleTimer = null;
|
|
992
|
+
this.releaseIdleConnection();
|
|
993
|
+
}, this.idleLingerMs);
|
|
994
|
+
this.idleTimer = timer;
|
|
995
|
+
timer.unref?.();
|
|
996
|
+
}
|
|
997
|
+
clearIdleTimer() {
|
|
998
|
+
if (this.idleTimer === null)
|
|
999
|
+
return;
|
|
1000
|
+
clearTimeout(this.idleTimer);
|
|
1001
|
+
this.idleTimer = null;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async function closeConnection(connection) {
|
|
1005
|
+
connection.detachDisconnect();
|
|
1006
|
+
const controlClosed = waitForSocketClose(connection.client.control);
|
|
1007
|
+
try {
|
|
1008
|
+
connection.client.close();
|
|
1009
|
+
connection.ownedConnection?.close();
|
|
1010
|
+
await controlClosed;
|
|
1011
|
+
} finally {
|
|
1012
|
+
connection.unregister();
|
|
819
1013
|
}
|
|
820
1014
|
}
|
|
1015
|
+
function waitForSocketClose(socket) {
|
|
1016
|
+
if (socket.readyState === 3)
|
|
1017
|
+
return Promise.resolve();
|
|
1018
|
+
return new Promise((resolve) => {
|
|
1019
|
+
let settled = false;
|
|
1020
|
+
let detach = () => {};
|
|
1021
|
+
const finish = () => {
|
|
1022
|
+
if (settled)
|
|
1023
|
+
return;
|
|
1024
|
+
settled = true;
|
|
1025
|
+
clearTimeout(timeout);
|
|
1026
|
+
detach();
|
|
1027
|
+
resolve();
|
|
1028
|
+
};
|
|
1029
|
+
if (socket.addEventListener) {
|
|
1030
|
+
socket.addEventListener("close", finish);
|
|
1031
|
+
detach = () => socket.removeEventListener?.("close", finish);
|
|
1032
|
+
} else if (socket.once) {
|
|
1033
|
+
socket.once("close", finish);
|
|
1034
|
+
detach = () => socket.off?.("close", finish);
|
|
1035
|
+
} else if (socket.on) {
|
|
1036
|
+
socket.on("close", finish);
|
|
1037
|
+
detach = () => socket.off?.("close", finish);
|
|
1038
|
+
} else {
|
|
1039
|
+
resolve();
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const timeout = setTimeout(finish, 1000);
|
|
1043
|
+
timeout.unref?.();
|
|
1044
|
+
if (socket.readyState === 3)
|
|
1045
|
+
finish();
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
821
1048
|
|
|
822
1049
|
// node_modules/@letta-ai/letta-code/dist/agent-presets.js
|
|
823
1050
|
var LETTA_CODE_ORIGIN_TAG = "origin:letta-code";
|
|
@@ -5486,7 +5713,7 @@ function buildCanUseToolContext(request, requestId) {
|
|
|
5486
5713
|
return context;
|
|
5487
5714
|
}
|
|
5488
5715
|
|
|
5489
|
-
// src/remote-
|
|
5716
|
+
// src/remote-session-protocol.ts
|
|
5490
5717
|
var FAILURE_STOP_REASONS = new Set([
|
|
5491
5718
|
"error",
|
|
5492
5719
|
"llm_api_error",
|
|
@@ -5881,856 +6108,930 @@ function normalizeSendMessage(message) {
|
|
|
5881
6108
|
return message;
|
|
5882
6109
|
}
|
|
5883
6110
|
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
controller = null;
|
|
5887
|
-
runtime = null;
|
|
5888
|
-
initialized = false;
|
|
5889
|
-
closed = false;
|
|
5890
|
-
activeTurnStartedAt = 0;
|
|
5891
|
-
_agentId = null;
|
|
5892
|
-
_sessionId = null;
|
|
5893
|
-
_conversationId = null;
|
|
5894
|
-
_model = "";
|
|
5895
|
-
_modelSettings = null;
|
|
6111
|
+
// src/remote-turn-coordinator.ts
|
|
6112
|
+
class RemoteTurnCoordinator {
|
|
5896
6113
|
label;
|
|
5897
6114
|
requestTimeoutMs;
|
|
6115
|
+
onDeviceStatus;
|
|
5898
6116
|
streamQueue = [];
|
|
5899
6117
|
streamResolvers = [];
|
|
5900
|
-
removeMessageHandler = null;
|
|
5901
6118
|
activeTurn = null;
|
|
5902
6119
|
pendingTurns = [];
|
|
5903
6120
|
nextTurnId = 0;
|
|
5904
6121
|
messageCounter = 0;
|
|
5905
6122
|
clientMessageCounter = 0;
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
constructor(mode, config) {
|
|
5910
|
-
this.mode = mode;
|
|
6123
|
+
closed = false;
|
|
6124
|
+
_activeTurnStartedAt = 0;
|
|
6125
|
+
constructor(config) {
|
|
5911
6126
|
this.label = config.label;
|
|
5912
6127
|
this.requestTimeoutMs = config.requestTimeoutMs;
|
|
6128
|
+
this.onDeviceStatus = config.onDeviceStatus;
|
|
5913
6129
|
}
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
throw new Error("Session already initialized");
|
|
5917
|
-
}
|
|
5918
|
-
if (this.closed) {
|
|
5919
|
-
throw new Error("Session is closed");
|
|
5920
|
-
}
|
|
5921
|
-
try {
|
|
5922
|
-
const init = await this.initializeRuntimeController();
|
|
5923
|
-
this.controller = init.controller;
|
|
5924
|
-
this.runtime = init.runtime;
|
|
5925
|
-
this._agentId = init.runtime.agent_id;
|
|
5926
|
-
this._conversationId = init.runtime.conversation_id;
|
|
5927
|
-
this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
|
|
5928
|
-
this._modelSettings = init.modelSettings ?? null;
|
|
5929
|
-
this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
|
|
5930
|
-
this.toolNames = init.tools;
|
|
5931
|
-
this.removeMessageHandler = this.controller.onMessage(this.handleProtocolMessage);
|
|
5932
|
-
this.initialized = true;
|
|
5933
|
-
await this.afterRuntimeInitialized();
|
|
5934
|
-
await this.applyPostInitializeOptions();
|
|
5935
|
-
const initMessage = {
|
|
5936
|
-
type: "init",
|
|
5937
|
-
agentId: init.runtime.agent_id,
|
|
5938
|
-
sessionId: this._sessionId,
|
|
5939
|
-
conversationId: init.runtime.conversation_id,
|
|
5940
|
-
model: this._model
|
|
5941
|
-
};
|
|
5942
|
-
if (this.toolNames !== undefined)
|
|
5943
|
-
initMessage.tools = this.toolNames;
|
|
5944
|
-
if (init.skillSources !== undefined) {
|
|
5945
|
-
initMessage.skillSources = init.skillSources;
|
|
5946
|
-
}
|
|
5947
|
-
return initMessage;
|
|
5948
|
-
} catch (error) {
|
|
5949
|
-
this.close();
|
|
5950
|
-
throw error;
|
|
5951
|
-
}
|
|
6130
|
+
get activeTurnStartedAt() {
|
|
6131
|
+
return this._activeTurnStartedAt;
|
|
5952
6132
|
}
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
await this.initialize();
|
|
5956
|
-
}
|
|
5957
|
-
if (!this.controller || !this.runtime) {
|
|
5958
|
-
throw new Error("Session is not initialized");
|
|
5959
|
-
}
|
|
5960
|
-
await this.beforeTurn();
|
|
5961
|
-
const turn = this.trackSentTurn(this.runtime);
|
|
5962
|
-
try {
|
|
5963
|
-
this.controller.sendTurnMessage(this.runtime, message, {
|
|
5964
|
-
clientMessageId: turn.clientMessageId
|
|
5965
|
-
});
|
|
5966
|
-
} catch (error) {
|
|
5967
|
-
this.removeTrackedTurn(turn);
|
|
5968
|
-
throw error;
|
|
5969
|
-
}
|
|
6133
|
+
hasInFlightTurn() {
|
|
6134
|
+
return this.activeTurn !== null || this.pendingTurns.length > 0;
|
|
5970
6135
|
}
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
error: "stream_closed",
|
|
5985
|
-
errorCode: "stream_closed",
|
|
5986
|
-
recoverable: false,
|
|
5987
|
-
errorDetail: "Stream ended before terminal result",
|
|
5988
|
-
durationMs: Date.now() - this.activeTurnStartedAt,
|
|
5989
|
-
conversationId: this._conversationId
|
|
6136
|
+
trackSentTurn(runtime) {
|
|
6137
|
+
const turn = {
|
|
6138
|
+
id: ++this.nextTurnId,
|
|
6139
|
+
runtime,
|
|
6140
|
+
clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
|
|
6141
|
+
queuedAt: Date.now(),
|
|
6142
|
+
startedAt: 0,
|
|
6143
|
+
assistantText: "",
|
|
6144
|
+
runIds: new Set,
|
|
6145
|
+
observedTurnEvidence: false,
|
|
6146
|
+
observedRequiresApprovalStop: false,
|
|
6147
|
+
abortRequested: false,
|
|
6148
|
+
timeout: null
|
|
5990
6149
|
};
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
if (!msg)
|
|
5996
|
-
break;
|
|
5997
|
-
yield msg;
|
|
5998
|
-
if (msg.type === "result")
|
|
5999
|
-
break;
|
|
6150
|
+
if (this.activeTurn) {
|
|
6151
|
+
this.pendingTurns.push(turn);
|
|
6152
|
+
} else {
|
|
6153
|
+
this.activateTurn(turn);
|
|
6000
6154
|
}
|
|
6155
|
+
return turn;
|
|
6001
6156
|
}
|
|
6002
|
-
|
|
6003
|
-
if (
|
|
6004
|
-
|
|
6005
|
-
if (
|
|
6157
|
+
removeTrackedTurn(turn) {
|
|
6158
|
+
if (turn.timeout)
|
|
6159
|
+
clearTimeout(turn.timeout);
|
|
6160
|
+
if (this.activeTurn === turn) {
|
|
6161
|
+
this.activeTurn = null;
|
|
6006
6162
|
return;
|
|
6163
|
+
}
|
|
6164
|
+
const index = this.pendingTurns.indexOf(turn);
|
|
6165
|
+
if (index !== -1)
|
|
6166
|
+
this.pendingTurns.splice(index, 1);
|
|
6167
|
+
}
|
|
6168
|
+
markAbortRequested() {
|
|
6007
6169
|
if (this.activeTurn)
|
|
6008
6170
|
this.activeTurn.abortRequested = true;
|
|
6009
|
-
await this.controller.abort(this.runtime);
|
|
6010
6171
|
}
|
|
6011
|
-
|
|
6012
|
-
if (!
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
if (
|
|
6016
|
-
|
|
6172
|
+
handleProtocolMessage(message, runtime) {
|
|
6173
|
+
if (!sameRuntime2(message, runtime))
|
|
6174
|
+
return;
|
|
6175
|
+
const statusRecord = deviceStatusRecord(message);
|
|
6176
|
+
if (statusRecord) {
|
|
6177
|
+
const status = toSessionDeviceStatus(statusRecord);
|
|
6178
|
+
if (status)
|
|
6179
|
+
this.onDeviceStatus(status);
|
|
6180
|
+
return;
|
|
6017
6181
|
}
|
|
6018
|
-
if (
|
|
6019
|
-
|
|
6182
|
+
if (message.type === "update_queue") {
|
|
6183
|
+
const sdkMessage2 = {
|
|
6184
|
+
type: "queue_update",
|
|
6185
|
+
queue: queueItems(message)
|
|
6186
|
+
};
|
|
6187
|
+
this.enqueue(sdkMessage2);
|
|
6188
|
+
return;
|
|
6020
6189
|
}
|
|
6021
|
-
if (
|
|
6022
|
-
|
|
6190
|
+
if (message.type === "update_loop_status") {
|
|
6191
|
+
this.handleLoopStatusMessage(message);
|
|
6192
|
+
return;
|
|
6023
6193
|
}
|
|
6024
|
-
|
|
6025
|
-
|
|
6194
|
+
const delta = streamDeltaRecord(message);
|
|
6195
|
+
if (!delta)
|
|
6026
6196
|
return;
|
|
6197
|
+
const active = this.activateNextTurnFromProtocol();
|
|
6198
|
+
if (active) {
|
|
6199
|
+
active.observedTurnEvidence = true;
|
|
6200
|
+
const runId = streamDeltaRunId2(delta);
|
|
6201
|
+
if (runId)
|
|
6202
|
+
active.runIds.add(runId);
|
|
6027
6203
|
}
|
|
6028
|
-
const
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6204
|
+
const sdkMessage = this.transformStreamDelta(delta);
|
|
6205
|
+
if (sdkMessage)
|
|
6206
|
+
this.enqueue(sdkMessage);
|
|
6207
|
+
this.handleTurnTerminalDelta(delta, sdkMessage);
|
|
6208
|
+
}
|
|
6209
|
+
nextMessage() {
|
|
6210
|
+
const next = this.streamQueue.shift();
|
|
6211
|
+
if (next)
|
|
6212
|
+
return Promise.resolve(next);
|
|
6213
|
+
if (this.closed)
|
|
6214
|
+
return Promise.resolve(null);
|
|
6215
|
+
return new Promise((resolve) => {
|
|
6216
|
+
this.streamResolvers.push(resolve);
|
|
6032
6217
|
});
|
|
6033
|
-
return response;
|
|
6034
6218
|
}
|
|
6035
|
-
|
|
6036
|
-
if (
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
if (
|
|
6040
|
-
|
|
6219
|
+
close() {
|
|
6220
|
+
if (this.closed)
|
|
6221
|
+
return;
|
|
6222
|
+
this.closed = true;
|
|
6223
|
+
if (this.activeTurn?.timeout)
|
|
6224
|
+
clearTimeout(this.activeTurn.timeout);
|
|
6225
|
+
for (const turn of this.pendingTurns) {
|
|
6226
|
+
if (turn.timeout)
|
|
6227
|
+
clearTimeout(turn.timeout);
|
|
6041
6228
|
}
|
|
6042
|
-
|
|
6229
|
+
this.activeTurn = null;
|
|
6230
|
+
this.pendingTurns.length = 0;
|
|
6231
|
+
this.resolveAll(null);
|
|
6043
6232
|
}
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6233
|
+
activateTurn(turn) {
|
|
6234
|
+
this.activeTurn = turn;
|
|
6235
|
+
turn.startedAt = Date.now();
|
|
6236
|
+
this._activeTurnStartedAt = turn.startedAt;
|
|
6237
|
+
if (this.requestTimeoutMs !== undefined) {
|
|
6238
|
+
turn.timeout = setTimeout(() => {
|
|
6239
|
+
this.failTurn(turn, `Timed out waiting for ${this.label} turn`);
|
|
6240
|
+
}, this.requestTimeoutMs);
|
|
6241
|
+
turn.timeout.unref?.();
|
|
6047
6242
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6243
|
+
}
|
|
6244
|
+
activateNextTurnFromProtocol() {
|
|
6245
|
+
if (this.activeTurn)
|
|
6246
|
+
return this.activeTurn;
|
|
6247
|
+
const next = this.pendingTurns.shift();
|
|
6248
|
+
if (!next)
|
|
6249
|
+
return null;
|
|
6250
|
+
this.activateTurn(next);
|
|
6251
|
+
return next;
|
|
6252
|
+
}
|
|
6253
|
+
failTurn(turn, detail) {
|
|
6254
|
+
if (this.activeTurn !== turn)
|
|
6255
|
+
return;
|
|
6256
|
+
this.enqueue({
|
|
6257
|
+
type: "error",
|
|
6258
|
+
message: detail,
|
|
6259
|
+
errorCode: "error",
|
|
6260
|
+
stopReason: "error",
|
|
6261
|
+
errorDetail: detail,
|
|
6262
|
+
recoverable: false
|
|
6263
|
+
});
|
|
6264
|
+
this.completeActiveTurn({
|
|
6265
|
+
runtime: turn.runtime,
|
|
6266
|
+
stopReason: "error",
|
|
6267
|
+
runIds: [...turn.runIds],
|
|
6268
|
+
success: false,
|
|
6269
|
+
detail,
|
|
6270
|
+
errorCode: "error"
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6273
|
+
completeActiveTurn(turn) {
|
|
6274
|
+
const active = this.activeTurn;
|
|
6275
|
+
if (!active)
|
|
6276
|
+
return;
|
|
6277
|
+
if (active.timeout) {
|
|
6278
|
+
clearTimeout(active.timeout);
|
|
6279
|
+
active.timeout = null;
|
|
6050
6280
|
}
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6281
|
+
this.enqueue(this.resultFromTurn(turn, active));
|
|
6282
|
+
this.activeTurn = null;
|
|
6283
|
+
}
|
|
6284
|
+
handleLoopStatusMessage(message) {
|
|
6285
|
+
const status = loopStatusValue(message);
|
|
6286
|
+
if (!status)
|
|
6287
|
+
return;
|
|
6288
|
+
const activeRunIds = loopStatusRunIds(message);
|
|
6289
|
+
const sdkMessage = {
|
|
6290
|
+
type: "loop_status",
|
|
6291
|
+
status,
|
|
6292
|
+
activeRunIds
|
|
6293
|
+
};
|
|
6294
|
+
this.enqueue(sdkMessage);
|
|
6295
|
+
const active = this.activeTurn;
|
|
6296
|
+
if (!active)
|
|
6297
|
+
return;
|
|
6298
|
+
for (const runId of activeRunIds)
|
|
6299
|
+
active.runIds.add(runId);
|
|
6300
|
+
const hadTurnEvidence = active.observedTurnEvidence || active.observedRequiresApprovalStop;
|
|
6301
|
+
if (!hadTurnEvidence)
|
|
6302
|
+
return;
|
|
6303
|
+
if (status === "WAITING_ON_APPROVAL") {
|
|
6304
|
+
this.completeActiveTurn({
|
|
6305
|
+
runtime: active.runtime,
|
|
6306
|
+
stopReason: "requires_approval",
|
|
6307
|
+
runIds: [...active.runIds]
|
|
6308
|
+
});
|
|
6309
|
+
return;
|
|
6060
6310
|
}
|
|
6061
|
-
if ("
|
|
6062
|
-
this.
|
|
6311
|
+
if (status === "WAITING_ON_INPUT" && active.abortRequested) {
|
|
6312
|
+
this.completeActiveTurn({
|
|
6313
|
+
runtime: active.runtime,
|
|
6314
|
+
stopReason: "interrupted",
|
|
6315
|
+
runIds: [...active.runIds],
|
|
6316
|
+
success: false,
|
|
6317
|
+
detail: "Interrupted",
|
|
6318
|
+
errorCode: "interrupted"
|
|
6319
|
+
});
|
|
6320
|
+
return;
|
|
6321
|
+
}
|
|
6322
|
+
if (status === "WAITING_ON_INPUT" && active.observedTurnEvidence) {
|
|
6323
|
+
this.completeActiveTurn({
|
|
6324
|
+
runtime: active.runtime,
|
|
6325
|
+
stopReason: null,
|
|
6326
|
+
runIds: [...active.runIds]
|
|
6327
|
+
});
|
|
6063
6328
|
}
|
|
6064
|
-
return result;
|
|
6065
6329
|
}
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6330
|
+
handleTurnTerminalDelta(delta, sdkMessage) {
|
|
6331
|
+
const active = this.activeTurn;
|
|
6332
|
+
if (!active)
|
|
6333
|
+
return;
|
|
6334
|
+
const messageType = streamDeltaMessageType2(delta);
|
|
6335
|
+
if (messageType === "stop_reason") {
|
|
6336
|
+
const stopReason = streamDeltaStopReason2(delta) ?? null;
|
|
6337
|
+
if (stopReason === "requires_approval") {
|
|
6338
|
+
active.observedRequiresApprovalStop = true;
|
|
6339
|
+
return;
|
|
6340
|
+
}
|
|
6341
|
+
this.completeActiveTurn({
|
|
6342
|
+
runtime: active.runtime,
|
|
6343
|
+
stopReason,
|
|
6344
|
+
runIds: [...active.runIds]
|
|
6345
|
+
});
|
|
6346
|
+
return;
|
|
6069
6347
|
}
|
|
6070
|
-
if (
|
|
6071
|
-
|
|
6348
|
+
if (sdkMessage?.type === "error") {
|
|
6349
|
+
this.completeActiveTurn({
|
|
6350
|
+
runtime: active.runtime,
|
|
6351
|
+
stopReason: sdkMessage.stopReason,
|
|
6352
|
+
runIds: [...active.runIds],
|
|
6353
|
+
success: false,
|
|
6354
|
+
detail: sdkMessage.errorDetail ?? sdkMessage.message,
|
|
6355
|
+
errorCode: sdkMessage.errorCode
|
|
6356
|
+
});
|
|
6072
6357
|
}
|
|
6073
|
-
return this.controller.recoverPendingApprovals(this.runtime, options);
|
|
6074
6358
|
}
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6359
|
+
transformStreamDelta(delta) {
|
|
6360
|
+
const messageType = typeof delta.message_type === "string" ? delta.message_type : undefined;
|
|
6361
|
+
const runId = typeof delta.run_id === "string" ? delta.run_id : undefined;
|
|
6362
|
+
const uuid = typeof delta.id === "string" ? delta.id : `${this.label}-${++this.messageCounter}`;
|
|
6363
|
+
if (messageType === "assistant_message") {
|
|
6364
|
+
const content = extractTextFromContent(delta.content);
|
|
6365
|
+
if (!content)
|
|
6366
|
+
return null;
|
|
6367
|
+
if (this.activeTurn)
|
|
6368
|
+
this.activeTurn.assistantText += content;
|
|
6369
|
+
return { type: "assistant", content, uuid, runId };
|
|
6078
6370
|
}
|
|
6079
|
-
if (
|
|
6080
|
-
|
|
6371
|
+
if (messageType === "reasoning_message") {
|
|
6372
|
+
const content = typeof delta.reasoning === "string" ? delta.reasoning : extractTextFromContent(delta.content);
|
|
6373
|
+
if (!content)
|
|
6374
|
+
return null;
|
|
6375
|
+
return { type: "reasoning", content, uuid, runId };
|
|
6081
6376
|
}
|
|
6082
|
-
if (
|
|
6083
|
-
|
|
6377
|
+
if (messageType === "tool_call_message" || messageType === "approval_request_message") {
|
|
6378
|
+
const toolCall = firstToolCall(delta);
|
|
6379
|
+
if (!toolCall)
|
|
6380
|
+
return null;
|
|
6381
|
+
const fn = toolCall.function && typeof toolCall.function === "object" ? toolCall.function : undefined;
|
|
6382
|
+
const toolCallId = (typeof toolCall.tool_call_id === "string" ? toolCall.tool_call_id : undefined) ?? (typeof toolCall.id === "string" ? toolCall.id : undefined);
|
|
6383
|
+
if (!toolCallId) {
|
|
6384
|
+
const detail = `Missing tool_call_id in ${messageType} (uuid=${uuid})`;
|
|
6385
|
+
return {
|
|
6386
|
+
type: "error",
|
|
6387
|
+
message: detail,
|
|
6388
|
+
errorCode: "protocol_error",
|
|
6389
|
+
stopReason: "protocol_error",
|
|
6390
|
+
runId,
|
|
6391
|
+
recoverable: false,
|
|
6392
|
+
errorDetail: detail
|
|
6393
|
+
};
|
|
6394
|
+
}
|
|
6395
|
+
const toolName = (typeof toolCall.name === "string" ? toolCall.name : undefined) ?? (typeof fn?.name === "string" ? fn.name : undefined) ?? "?";
|
|
6396
|
+
const { input, raw } = toolInputFromArguments(toolCall.arguments ?? fn?.arguments);
|
|
6397
|
+
return {
|
|
6398
|
+
type: "tool_call",
|
|
6399
|
+
toolCallId,
|
|
6400
|
+
toolName,
|
|
6401
|
+
toolInput: input,
|
|
6402
|
+
rawArguments: raw,
|
|
6403
|
+
uuid,
|
|
6404
|
+
runId
|
|
6405
|
+
};
|
|
6084
6406
|
}
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6407
|
+
if (messageType === "tool_return_message") {
|
|
6408
|
+
const toolReturn = firstToolReturn(delta) ?? delta;
|
|
6409
|
+
const toolCallId = (typeof delta.tool_call_id === "string" ? delta.tool_call_id : undefined) ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
|
|
6410
|
+
if (!toolCallId)
|
|
6411
|
+
return null;
|
|
6412
|
+
const content = extractTextFromContent(delta.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
|
|
6413
|
+
const status = typeof delta.status === "string" ? delta.status : toolReturn.status;
|
|
6414
|
+
return {
|
|
6415
|
+
type: "tool_result",
|
|
6416
|
+
toolCallId,
|
|
6417
|
+
content,
|
|
6418
|
+
isError: status === "error",
|
|
6419
|
+
uuid,
|
|
6420
|
+
runId
|
|
6421
|
+
};
|
|
6093
6422
|
}
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6423
|
+
if (messageType === "error_message" || messageType === "loop_error") {
|
|
6424
|
+
const detail = (typeof delta.detail === "string" ? delta.detail : undefined) ?? (typeof delta.message === "string" ? delta.message : undefined) ?? `${this.label} turn failed`;
|
|
6425
|
+
const stopReason = (typeof delta.stop_reason === "string" ? delta.stop_reason : undefined) ?? (typeof delta.error_type === "string" ? delta.error_type : undefined) ?? "error";
|
|
6426
|
+
const approvalConflict = isApprovalConflictSignal({
|
|
6427
|
+
detail,
|
|
6428
|
+
message: typeof delta.message === "string" ? delta.message : undefined,
|
|
6429
|
+
stopReason
|
|
6430
|
+
});
|
|
6431
|
+
return {
|
|
6432
|
+
type: "error",
|
|
6433
|
+
message: detail,
|
|
6434
|
+
errorCode: approvalConflict ? "approval_conflict" : toSdkErrorCode(stopReason),
|
|
6435
|
+
approvalConflict: approvalConflict || undefined,
|
|
6436
|
+
recoverable: approvalConflict ? true : false,
|
|
6437
|
+
errorDetail: detail,
|
|
6438
|
+
stopReason,
|
|
6439
|
+
runId
|
|
6440
|
+
};
|
|
6102
6441
|
}
|
|
6103
|
-
if (
|
|
6104
|
-
|
|
6442
|
+
if (messageType === "retry") {
|
|
6443
|
+
return {
|
|
6444
|
+
type: "retry",
|
|
6445
|
+
reason: typeof delta.reason === "string" ? delta.reason : "error",
|
|
6446
|
+
attempt: typeof delta.attempt === "number" ? delta.attempt : 0,
|
|
6447
|
+
maxAttempts: typeof delta.max_attempts === "number" ? delta.max_attempts : 0,
|
|
6448
|
+
delayMs: typeof delta.delay_ms === "number" ? delta.delay_ms : 0,
|
|
6449
|
+
runId
|
|
6450
|
+
};
|
|
6105
6451
|
}
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
throw new Error("Invalid device status timeout. Expected a positive integer.");
|
|
6452
|
+
if (messageType === "stop_reason" || messageType === "ping") {
|
|
6453
|
+
return null;
|
|
6109
6454
|
}
|
|
6110
|
-
return
|
|
6455
|
+
return {
|
|
6456
|
+
type: "stream_event",
|
|
6457
|
+
event: delta,
|
|
6458
|
+
uuid
|
|
6459
|
+
};
|
|
6111
6460
|
}
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6461
|
+
resultFromTurn(turn, tracker) {
|
|
6462
|
+
const stopReason = turn.stopReason ?? (turn.success === false ? "error" : undefined);
|
|
6463
|
+
const approvalConflict = isApprovalConflictSignal({
|
|
6464
|
+
detail: turn.detail,
|
|
6465
|
+
stopReason
|
|
6466
|
+
});
|
|
6467
|
+
const success = turn.success !== undefined ? turn.success && !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "") : !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "");
|
|
6468
|
+
const errorCode = approvalConflict ? "approval_conflict" : turn.errorCode ?? toSdkErrorCode(stopReason);
|
|
6469
|
+
return {
|
|
6470
|
+
type: "result",
|
|
6471
|
+
success,
|
|
6472
|
+
result: success ? tracker?.assistantText || undefined : undefined,
|
|
6473
|
+
error: success ? undefined : errorCode ?? stopReason ?? "error",
|
|
6474
|
+
errorCode: success ? undefined : errorCode ?? "error",
|
|
6475
|
+
approvalConflict: approvalConflict || undefined,
|
|
6476
|
+
recoverable: approvalConflict ? true : success ? undefined : false,
|
|
6477
|
+
errorDetail: success ? undefined : turn.detail,
|
|
6478
|
+
stopReason,
|
|
6479
|
+
durationMs: Date.now() - (tracker?.startedAt || this._activeTurnStartedAt),
|
|
6480
|
+
conversationId: turn.runtime.conversation_id,
|
|
6481
|
+
runIds: turn.runIds.length > 0 ? turn.runIds : undefined
|
|
6119
6482
|
};
|
|
6120
6483
|
}
|
|
6121
|
-
|
|
6122
|
-
const
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
return
|
|
6484
|
+
enqueue(message) {
|
|
6485
|
+
const resolver = this.streamResolvers.shift();
|
|
6486
|
+
if (resolver) {
|
|
6487
|
+
resolver(message);
|
|
6488
|
+
return;
|
|
6126
6489
|
}
|
|
6127
|
-
|
|
6128
|
-
let status = null;
|
|
6129
|
-
let syncAcknowledged = false;
|
|
6130
|
-
let settled = false;
|
|
6131
|
-
const cleanup = () => {
|
|
6132
|
-
clearTimeout(timer);
|
|
6133
|
-
unsubscribe();
|
|
6134
|
-
this.deviceStatusRefreshCancels.delete(cancel);
|
|
6135
|
-
};
|
|
6136
|
-
const rejectOnce = (error) => {
|
|
6137
|
-
if (settled)
|
|
6138
|
-
return;
|
|
6139
|
-
settled = true;
|
|
6140
|
-
cleanup();
|
|
6141
|
-
reject(error);
|
|
6142
|
-
};
|
|
6143
|
-
const resolveIfComplete = () => {
|
|
6144
|
-
if (settled || !syncAcknowledged || !status)
|
|
6145
|
-
return;
|
|
6146
|
-
settled = true;
|
|
6147
|
-
cleanup();
|
|
6148
|
-
resolve(status);
|
|
6149
|
-
};
|
|
6150
|
-
const cancel = (error) => rejectOnce(error);
|
|
6151
|
-
const timer = setTimeout(() => {
|
|
6152
|
-
rejectOnce(new Error(`Timed out waiting for ${this.label} device status`));
|
|
6153
|
-
}, timeoutMs);
|
|
6154
|
-
timer.unref?.();
|
|
6155
|
-
const unsubscribe = this.onDeviceStatus((nextStatus) => {
|
|
6156
|
-
status = nextStatus;
|
|
6157
|
-
resolveIfComplete();
|
|
6158
|
-
});
|
|
6159
|
-
this.deviceStatusRefreshCancels.add(cancel);
|
|
6160
|
-
controller.request("sync", {
|
|
6161
|
-
runtime,
|
|
6162
|
-
recover_approvals: false,
|
|
6163
|
-
force_device_status: true
|
|
6164
|
-
}, {
|
|
6165
|
-
timeoutMs,
|
|
6166
|
-
predicate: (message) => message.type === "sync_response"
|
|
6167
|
-
}).then((response) => {
|
|
6168
|
-
if (response.success === false) {
|
|
6169
|
-
rejectOnce(new Error(typeof response.error === "string" ? response.error : `Failed to refresh ${this.label} device status`));
|
|
6170
|
-
return;
|
|
6171
|
-
}
|
|
6172
|
-
syncAcknowledged = true;
|
|
6173
|
-
resolveIfComplete();
|
|
6174
|
-
}, (error) => {
|
|
6175
|
-
rejectOnce(error instanceof Error ? error : new Error(String(error)));
|
|
6176
|
-
});
|
|
6177
|
-
});
|
|
6490
|
+
this.streamQueue.push(message);
|
|
6178
6491
|
}
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6492
|
+
resolveAll(value) {
|
|
6493
|
+
for (const resolve of this.streamResolvers.splice(0)) {
|
|
6494
|
+
resolve(value);
|
|
6182
6495
|
}
|
|
6183
|
-
|
|
6184
|
-
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
// src/remote-client-session-core.ts
|
|
6499
|
+
class RemoteClientSessionCore {
|
|
6500
|
+
mode;
|
|
6501
|
+
controller = null;
|
|
6502
|
+
runtime = null;
|
|
6503
|
+
initialized = false;
|
|
6504
|
+
closed = false;
|
|
6505
|
+
_agentId = null;
|
|
6506
|
+
_sessionId = null;
|
|
6507
|
+
_conversationId = null;
|
|
6508
|
+
_model = "";
|
|
6509
|
+
_modelSettings = null;
|
|
6510
|
+
label;
|
|
6511
|
+
requestTimeoutMs;
|
|
6512
|
+
initializePromise = null;
|
|
6513
|
+
removeMessageHandler = null;
|
|
6514
|
+
turns;
|
|
6515
|
+
toolNames;
|
|
6516
|
+
deviceStatusListeners = new Set;
|
|
6517
|
+
deviceStatusRefreshCancels = new Set;
|
|
6518
|
+
constructor(mode, config) {
|
|
6519
|
+
this.mode = mode;
|
|
6520
|
+
this.label = config.label;
|
|
6521
|
+
this.requestTimeoutMs = config.requestTimeoutMs;
|
|
6522
|
+
this.turns = new RemoteTurnCoordinator({
|
|
6523
|
+
label: config.label,
|
|
6524
|
+
requestTimeoutMs: config.requestTimeoutMs,
|
|
6525
|
+
onDeviceStatus: (status) => this.emitDeviceStatus(status)
|
|
6526
|
+
});
|
|
6527
|
+
}
|
|
6528
|
+
async initialize() {
|
|
6529
|
+
if (this.closed) {
|
|
6530
|
+
throw new Error("Session is closed");
|
|
6185
6531
|
}
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
throw new Error("No conversation id available for listMessages()");
|
|
6532
|
+
if (this.initializePromise) {
|
|
6533
|
+
return this.initializePromise;
|
|
6189
6534
|
}
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
async bootstrapState(options = {}) {
|
|
6193
|
-
if (!this.initialized) {
|
|
6194
|
-
await this.initialize();
|
|
6535
|
+
if (this.initialized) {
|
|
6536
|
+
throw new Error("Session already initialized");
|
|
6195
6537
|
}
|
|
6196
|
-
const
|
|
6197
|
-
|
|
6198
|
-
|
|
6538
|
+
const attempt = this.performInitialize();
|
|
6539
|
+
const memo = attempt.catch((error) => {
|
|
6540
|
+
this.cleanupFailedInitialize();
|
|
6541
|
+
throw error;
|
|
6542
|
+
}).finally(() => {
|
|
6543
|
+
if (this.initializePromise === memo) {
|
|
6544
|
+
this.initializePromise = null;
|
|
6545
|
+
}
|
|
6199
6546
|
});
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6547
|
+
this.initializePromise = memo;
|
|
6548
|
+
return memo;
|
|
6549
|
+
}
|
|
6550
|
+
async performInitialize() {
|
|
6551
|
+
const init = await this.initializeRuntimeController();
|
|
6552
|
+
this.controller = init.controller;
|
|
6553
|
+
this.runtime = init.runtime;
|
|
6554
|
+
this._agentId = init.runtime.agent_id;
|
|
6555
|
+
this._conversationId = init.runtime.conversation_id;
|
|
6556
|
+
this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
|
|
6557
|
+
this._modelSettings = init.modelSettings ?? null;
|
|
6558
|
+
this._model = typeof init.model === "string" ? init.model : typeof this._modelSettings?.model === "string" ? this._modelSettings.model : "";
|
|
6559
|
+
this.toolNames = init.tools;
|
|
6560
|
+
this.removeMessageHandler = this.controller.onMessage((message) => {
|
|
6561
|
+
if (this.runtime)
|
|
6562
|
+
this.turns.handleProtocolMessage(message, this.runtime);
|
|
6563
|
+
});
|
|
6564
|
+
await this.afterRuntimeInitialized();
|
|
6565
|
+
await this.applyPostInitializeOptions();
|
|
6566
|
+
if (this.closed) {
|
|
6567
|
+
throw new Error("Session is closed");
|
|
6211
6568
|
}
|
|
6212
|
-
|
|
6213
|
-
|
|
6569
|
+
this.initialized = true;
|
|
6570
|
+
const initMessage = {
|
|
6571
|
+
type: "init",
|
|
6572
|
+
agentId: init.runtime.agent_id,
|
|
6573
|
+
sessionId: this._sessionId,
|
|
6574
|
+
conversationId: init.runtime.conversation_id,
|
|
6575
|
+
model: this._model
|
|
6576
|
+
};
|
|
6577
|
+
if (this.toolNames !== undefined)
|
|
6578
|
+
initMessage.tools = this.toolNames;
|
|
6579
|
+
if (init.skillSources !== undefined) {
|
|
6580
|
+
initMessage.skillSources = init.skillSources;
|
|
6214
6581
|
}
|
|
6215
|
-
return
|
|
6582
|
+
return initMessage;
|
|
6216
6583
|
}
|
|
6217
|
-
|
|
6218
|
-
if (this.closed)
|
|
6219
|
-
return;
|
|
6220
|
-
this.closed = true;
|
|
6584
|
+
cleanupFailedInitialize() {
|
|
6221
6585
|
this.removeMessageHandler?.();
|
|
6222
6586
|
this.removeMessageHandler = null;
|
|
6223
|
-
if (this.activeTurn?.timeout)
|
|
6224
|
-
clearTimeout(this.activeTurn.timeout);
|
|
6225
|
-
for (const turn of this.pendingTurns) {
|
|
6226
|
-
if (turn.timeout)
|
|
6227
|
-
clearTimeout(turn.timeout);
|
|
6228
|
-
}
|
|
6229
|
-
this.activeTurn = null;
|
|
6230
|
-
this.pendingTurns.length = 0;
|
|
6231
|
-
for (const cancel of [...this.deviceStatusRefreshCancels]) {
|
|
6232
|
-
cancel(new Error(`Session closed while waiting for ${this.label} device status`));
|
|
6233
|
-
}
|
|
6234
|
-
this.deviceStatusRefreshCancels.clear();
|
|
6235
|
-
this.deviceStatusListeners.clear();
|
|
6236
6587
|
this.controller?.close();
|
|
6237
6588
|
this.controller = null;
|
|
6238
6589
|
this.onCoreClose();
|
|
6239
|
-
this.
|
|
6590
|
+
this.runtime = null;
|
|
6591
|
+
this._agentId = null;
|
|
6592
|
+
this._conversationId = null;
|
|
6593
|
+
this._sessionId = null;
|
|
6594
|
+
this._modelSettings = null;
|
|
6595
|
+
this._model = "";
|
|
6596
|
+
this.toolNames = undefined;
|
|
6597
|
+
this.initialized = false;
|
|
6240
6598
|
}
|
|
6241
|
-
|
|
6242
|
-
|
|
6599
|
+
async send(message) {
|
|
6600
|
+
if (!this.initialized) {
|
|
6601
|
+
await this.initialize();
|
|
6602
|
+
}
|
|
6603
|
+
if (!this.controller || !this.runtime) {
|
|
6604
|
+
throw new Error("Session is not initialized");
|
|
6605
|
+
}
|
|
6606
|
+
await this.beforeTurn();
|
|
6607
|
+
const turn = this.turns.trackSentTurn(this.runtime);
|
|
6608
|
+
try {
|
|
6609
|
+
this.controller.sendTurnMessage(this.runtime, message, {
|
|
6610
|
+
clientMessageId: turn.clientMessageId
|
|
6611
|
+
});
|
|
6612
|
+
} catch (error) {
|
|
6613
|
+
this.turns.removeTrackedTurn(turn);
|
|
6614
|
+
throw error;
|
|
6615
|
+
}
|
|
6243
6616
|
}
|
|
6244
|
-
|
|
6245
|
-
|
|
6617
|
+
async runTurn(message, _options = {}) {
|
|
6618
|
+
if (this.turns.hasInFlightTurn()) {
|
|
6619
|
+
throw new Error(`A turn is already in flight for this ${this.label} session. Use send() and stream() to let the listener queue messages.`);
|
|
6620
|
+
}
|
|
6621
|
+
await this.send(message);
|
|
6622
|
+
for await (const msg of this.stream()) {
|
|
6623
|
+
if (msg.type === "result") {
|
|
6624
|
+
return msg;
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6627
|
+
return {
|
|
6628
|
+
type: "result",
|
|
6629
|
+
success: false,
|
|
6630
|
+
error: "stream_closed",
|
|
6631
|
+
errorCode: "stream_closed",
|
|
6632
|
+
recoverable: false,
|
|
6633
|
+
errorDetail: "Stream ended before terminal result",
|
|
6634
|
+
durationMs: Date.now() - this.turns.activeTurnStartedAt,
|
|
6635
|
+
conversationId: this._conversationId
|
|
6636
|
+
};
|
|
6246
6637
|
}
|
|
6247
|
-
|
|
6248
|
-
|
|
6638
|
+
async* stream() {
|
|
6639
|
+
while (true) {
|
|
6640
|
+
const msg = await this.turns.nextMessage();
|
|
6641
|
+
if (!msg)
|
|
6642
|
+
break;
|
|
6643
|
+
yield msg;
|
|
6644
|
+
if (msg.type === "result")
|
|
6645
|
+
break;
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
async abort() {
|
|
6649
|
+
if (!this.initialized)
|
|
6650
|
+
return;
|
|
6651
|
+
if (!this.controller || !this.runtime)
|
|
6652
|
+
return;
|
|
6653
|
+
this.turns.markAbortRequested();
|
|
6654
|
+
await this.controller.abort(this.runtime);
|
|
6655
|
+
}
|
|
6656
|
+
async sendCommand(command, options) {
|
|
6657
|
+
if (!this.initialized) {
|
|
6658
|
+
await this.initialize();
|
|
6659
|
+
}
|
|
6660
|
+
if (!this.controller) {
|
|
6661
|
+
throw new Error("Session is not initialized");
|
|
6662
|
+
}
|
|
6663
|
+
if (!command || typeof command !== "object" || Array.isArray(command)) {
|
|
6664
|
+
throw new Error("Invalid command. Expected a protocol command object.");
|
|
6665
|
+
}
|
|
6666
|
+
if (typeof command.type !== "string" || command.type.length === 0) {
|
|
6667
|
+
throw new Error("Invalid command. Expected a non-empty type.");
|
|
6668
|
+
}
|
|
6669
|
+
if (!options || !options.responseType && !options.predicate && options.timeoutMs === undefined) {
|
|
6670
|
+
this.controller.send(command);
|
|
6671
|
+
return;
|
|
6672
|
+
}
|
|
6673
|
+
const { type, ...body } = command;
|
|
6674
|
+
const response = await this.controller.request(type, body, {
|
|
6675
|
+
...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {},
|
|
6676
|
+
predicate: options.predicate ? (message) => options.predicate?.(message) === true : options.responseType ? (message) => message.type === options.responseType : undefined
|
|
6677
|
+
});
|
|
6678
|
+
return response;
|
|
6249
6679
|
}
|
|
6250
|
-
async
|
|
6251
|
-
this.
|
|
6680
|
+
async listModels() {
|
|
6681
|
+
if (!this.initialized) {
|
|
6682
|
+
await this.initialize();
|
|
6683
|
+
}
|
|
6684
|
+
if (!this.controller) {
|
|
6685
|
+
throw new Error("Session is not initialized");
|
|
6686
|
+
}
|
|
6687
|
+
return this.controller.listModels();
|
|
6252
6688
|
}
|
|
6253
|
-
async
|
|
6689
|
+
async updateModel(update) {
|
|
6254
6690
|
if (!this.initialized) {
|
|
6255
6691
|
await this.initialize();
|
|
6256
6692
|
}
|
|
6257
6693
|
if (!this.controller || !this.runtime) {
|
|
6258
6694
|
throw new Error("Session is not initialized");
|
|
6259
6695
|
}
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
if (
|
|
6264
|
-
|
|
6265
|
-
if (mode !== undefined)
|
|
6266
|
-
payload.mode = mode;
|
|
6696
|
+
return this.applyModelUpdate(update);
|
|
6697
|
+
}
|
|
6698
|
+
async applyModelUpdate(update) {
|
|
6699
|
+
if (!this.controller || !this.runtime) {
|
|
6700
|
+
throw new Error("Session is not initialized");
|
|
6267
6701
|
}
|
|
6268
|
-
|
|
6269
|
-
|
|
6702
|
+
const normalized = normalizeUpdateModelInput(update);
|
|
6703
|
+
const payload = await this.resolveUpdateModelPayload(normalized);
|
|
6704
|
+
const result = await this.controller.updateModel(this.runtime, payload);
|
|
6705
|
+
if (result.modelHandle !== undefined) {
|
|
6706
|
+
this._model = result.modelHandle;
|
|
6707
|
+
} else if (payload.model_handle !== undefined) {
|
|
6708
|
+
this._model = payload.model_handle;
|
|
6709
|
+
} else if (typeof result.modelSettings?.model === "string") {
|
|
6710
|
+
this._model = result.modelSettings.model;
|
|
6270
6711
|
}
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
});
|
|
6712
|
+
if ("modelSettings" in result) {
|
|
6713
|
+
this._modelSettings = result.modelSettings ?? null;
|
|
6714
|
+
}
|
|
6715
|
+
return result;
|
|
6276
6716
|
}
|
|
6277
|
-
async
|
|
6717
|
+
async recoverPendingApprovals(options = {}) {
|
|
6278
6718
|
if (!this.initialized) {
|
|
6279
6719
|
await this.initialize();
|
|
6280
6720
|
}
|
|
6281
6721
|
if (!this.controller || !this.runtime) {
|
|
6282
6722
|
throw new Error("Session is not initialized");
|
|
6283
6723
|
}
|
|
6284
|
-
|
|
6285
|
-
runtime: this.runtime,
|
|
6286
|
-
toolset_preference: toolsetPreference
|
|
6287
|
-
}, { predicate: (message) => message.type === "update_toolset_response" });
|
|
6288
|
-
ensureSuccess(response, "Failed to update toolset");
|
|
6289
|
-
}
|
|
6290
|
-
async afterRuntimeInitialized() {}
|
|
6291
|
-
async beforeTurn() {}
|
|
6292
|
-
onCoreClose() {}
|
|
6293
|
-
currentOptions() {
|
|
6294
|
-
return this.mode.options;
|
|
6295
|
-
}
|
|
6296
|
-
shouldEnableMemfs(options) {
|
|
6297
|
-
return false;
|
|
6298
|
-
}
|
|
6299
|
-
enableMemfsBody() {
|
|
6300
|
-
if (!this.runtime)
|
|
6301
|
-
return {};
|
|
6302
|
-
return { agent_id: this.runtime.agent_id };
|
|
6303
|
-
}
|
|
6304
|
-
setModel(model) {
|
|
6305
|
-
this._model = model;
|
|
6724
|
+
return this.controller.recoverPendingApprovals(this.runtime, options);
|
|
6306
6725
|
}
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
id
|
|
6310
|
-
runtime,
|
|
6311
|
-
clientMessageId: `sdk-message-${Date.now()}-${++this.clientMessageCounter}`,
|
|
6312
|
-
queuedAt: Date.now(),
|
|
6313
|
-
startedAt: 0,
|
|
6314
|
-
assistantText: "",
|
|
6315
|
-
runIds: new Set,
|
|
6316
|
-
observedTurnEvidence: false,
|
|
6317
|
-
observedRequiresApprovalStop: false,
|
|
6318
|
-
abortRequested: false,
|
|
6319
|
-
timeout: null
|
|
6320
|
-
};
|
|
6321
|
-
if (this.activeTurn) {
|
|
6322
|
-
this.pendingTurns.push(turn);
|
|
6323
|
-
} else {
|
|
6324
|
-
this.activateTurn(turn);
|
|
6726
|
+
async removeQueuedMessage(itemId) {
|
|
6727
|
+
if (typeof itemId !== "string" || itemId.trim().length === 0) {
|
|
6728
|
+
throw new Error("Invalid queue item id. Expected a non-empty string.");
|
|
6325
6729
|
}
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
activateTurn(turn) {
|
|
6329
|
-
this.activeTurn = turn;
|
|
6330
|
-
turn.startedAt = Date.now();
|
|
6331
|
-
this.activeTurnStartedAt = turn.startedAt;
|
|
6332
|
-
if (this.requestTimeoutMs !== undefined) {
|
|
6333
|
-
turn.timeout = setTimeout(() => {
|
|
6334
|
-
this.failTurn(turn, `Timed out waiting for ${this.label} turn`);
|
|
6335
|
-
}, this.requestTimeoutMs);
|
|
6336
|
-
turn.timeout.unref?.();
|
|
6730
|
+
if (!this.initialized) {
|
|
6731
|
+
await this.initialize();
|
|
6337
6732
|
}
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
if (this.activeTurn)
|
|
6341
|
-
return this.activeTurn;
|
|
6342
|
-
const next = this.pendingTurns.shift();
|
|
6343
|
-
if (!next)
|
|
6344
|
-
return null;
|
|
6345
|
-
this.activateTurn(next);
|
|
6346
|
-
return next;
|
|
6347
|
-
}
|
|
6348
|
-
removeTrackedTurn(turn) {
|
|
6349
|
-
if (turn.timeout)
|
|
6350
|
-
clearTimeout(turn.timeout);
|
|
6351
|
-
if (this.activeTurn === turn) {
|
|
6352
|
-
this.activeTurn = null;
|
|
6353
|
-
return;
|
|
6733
|
+
if (!this.controller || !this.runtime) {
|
|
6734
|
+
throw new Error("Session is not initialized");
|
|
6354
6735
|
}
|
|
6355
|
-
const
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
if (this.activeTurn !== turn)
|
|
6361
|
-
return;
|
|
6362
|
-
this.enqueue({
|
|
6363
|
-
type: "error",
|
|
6364
|
-
message: detail,
|
|
6365
|
-
errorCode: "error",
|
|
6366
|
-
stopReason: "error",
|
|
6367
|
-
errorDetail: detail,
|
|
6368
|
-
recoverable: false
|
|
6369
|
-
});
|
|
6370
|
-
this.completeActiveTurn({
|
|
6371
|
-
runtime: turn.runtime,
|
|
6372
|
-
stopReason: "error",
|
|
6373
|
-
runIds: [...turn.runIds],
|
|
6374
|
-
success: false,
|
|
6375
|
-
detail,
|
|
6376
|
-
errorCode: "error"
|
|
6736
|
+
const response = await this.controller.request("remove_queue_item", {
|
|
6737
|
+
runtime: this.runtime,
|
|
6738
|
+
item_id: itemId
|
|
6739
|
+
}, {
|
|
6740
|
+
predicate: (message) => message.type === "remove_queue_item_response" && message.item_id === itemId
|
|
6377
6741
|
});
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
const active = this.activeTurn;
|
|
6381
|
-
if (!active)
|
|
6382
|
-
return;
|
|
6383
|
-
if (active.timeout) {
|
|
6384
|
-
clearTimeout(active.timeout);
|
|
6385
|
-
active.timeout = null;
|
|
6742
|
+
if (typeof response.success !== "boolean") {
|
|
6743
|
+
throw new Error("Invalid remove_queue_item_response from runtime");
|
|
6386
6744
|
}
|
|
6387
|
-
|
|
6388
|
-
|
|
6745
|
+
return {
|
|
6746
|
+
itemId: typeof response.item_id === "string" ? response.item_id : itemId,
|
|
6747
|
+
removed: response.success
|
|
6748
|
+
};
|
|
6389
6749
|
}
|
|
6390
|
-
async
|
|
6391
|
-
if (
|
|
6392
|
-
|
|
6750
|
+
async getDeviceStatus(options = {}) {
|
|
6751
|
+
if (!this.initialized) {
|
|
6752
|
+
await this.initialize();
|
|
6393
6753
|
}
|
|
6394
|
-
if (!this.controller) {
|
|
6754
|
+
if (!this.controller || !this.runtime) {
|
|
6395
6755
|
throw new Error("Session is not initialized");
|
|
6396
6756
|
}
|
|
6397
|
-
const
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
let baseEntry;
|
|
6401
|
-
let explicitHandle;
|
|
6402
|
-
let targetHandle;
|
|
6403
|
-
if (input.modelId !== undefined) {
|
|
6404
|
-
baseEntry = byId.get(input.modelId);
|
|
6405
|
-
explicitHandle = input.modelHandle;
|
|
6406
|
-
targetHandle = baseEntry?.handle ?? toBaseModelHandle(input.modelHandle, aliases);
|
|
6407
|
-
} else if (input.modelHandle !== undefined) {
|
|
6408
|
-
explicitHandle = input.modelHandle;
|
|
6409
|
-
targetHandle = toBaseModelHandle(input.modelHandle, aliases);
|
|
6410
|
-
} else if (input.model !== undefined) {
|
|
6411
|
-
baseEntry = byId.get(input.model);
|
|
6412
|
-
if (baseEntry) {
|
|
6413
|
-
targetHandle = baseEntry.handle;
|
|
6414
|
-
} else {
|
|
6415
|
-
explicitHandle = input.model;
|
|
6416
|
-
targetHandle = toBaseModelHandle(input.model, aliases);
|
|
6417
|
-
}
|
|
6418
|
-
} else {
|
|
6419
|
-
explicitHandle = this._model || undefined;
|
|
6420
|
-
targetHandle = toBaseModelHandle(this._model || undefined, aliases);
|
|
6421
|
-
}
|
|
6422
|
-
if (!targetHandle) {
|
|
6423
|
-
throw new Error("reasoningEffort requires a current model or explicit model/modelId/modelHandle.");
|
|
6424
|
-
}
|
|
6425
|
-
const candidates = catalog.entries.filter((entry) => entry.handle === targetHandle || entry.handle === explicitHandle);
|
|
6426
|
-
if (candidates.length === 0) {
|
|
6427
|
-
throw new Error(`reasoningEffort requires a model from listModels(); no catalog entry found for ${targetHandle}.`);
|
|
6428
|
-
}
|
|
6429
|
-
const contextWindow = getContextWindow(baseEntry?.updateArgs) ?? getContextWindow(this._modelSettings);
|
|
6430
|
-
const scopedCandidates = sameContextCandidates(candidates, contextWindow);
|
|
6431
|
-
const matchingEntry = scopedCandidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort) ?? candidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort);
|
|
6432
|
-
if (!matchingEntry) {
|
|
6433
|
-
throw new Error(`No ${input.reasoningEffort} reasoning tier found for model ${targetHandle}.`);
|
|
6757
|
+
const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs ?? 30000;
|
|
6758
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
6759
|
+
throw new Error("Invalid device status timeout. Expected a positive integer.");
|
|
6434
6760
|
}
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6761
|
+
return this.refreshDeviceStatus(timeoutMs);
|
|
6762
|
+
}
|
|
6763
|
+
onDeviceStatus(listener) {
|
|
6764
|
+
if (typeof listener !== "function") {
|
|
6765
|
+
throw new Error("Invalid device status listener. Expected a function.");
|
|
6438
6766
|
}
|
|
6439
|
-
|
|
6767
|
+
this.deviceStatusListeners.add(listener);
|
|
6768
|
+
return () => {
|
|
6769
|
+
this.deviceStatusListeners.delete(listener);
|
|
6770
|
+
};
|
|
6440
6771
|
}
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6772
|
+
refreshDeviceStatus(timeoutMs) {
|
|
6773
|
+
const controller = this.controller;
|
|
6774
|
+
const runtime = this.runtime;
|
|
6775
|
+
if (!controller || !runtime) {
|
|
6776
|
+
return Promise.reject(new Error("Session is not initialized"));
|
|
6777
|
+
}
|
|
6778
|
+
return new Promise((resolve, reject) => {
|
|
6779
|
+
let status = null;
|
|
6780
|
+
let syncAcknowledged = false;
|
|
6781
|
+
let settled = false;
|
|
6782
|
+
const cleanup = () => {
|
|
6783
|
+
clearTimeout(timer);
|
|
6784
|
+
unsubscribe();
|
|
6785
|
+
this.deviceStatusRefreshCancels.delete(cancel);
|
|
6786
|
+
};
|
|
6787
|
+
const rejectOnce = (error) => {
|
|
6788
|
+
if (settled)
|
|
6789
|
+
return;
|
|
6790
|
+
settled = true;
|
|
6791
|
+
cleanup();
|
|
6792
|
+
reject(error);
|
|
6793
|
+
};
|
|
6794
|
+
const resolveIfComplete = () => {
|
|
6795
|
+
if (settled || !syncAcknowledged || !status)
|
|
6796
|
+
return;
|
|
6797
|
+
settled = true;
|
|
6798
|
+
cleanup();
|
|
6799
|
+
resolve(status);
|
|
6800
|
+
};
|
|
6801
|
+
const cancel = (error) => rejectOnce(error);
|
|
6802
|
+
const timer = setTimeout(() => {
|
|
6803
|
+
rejectOnce(new Error(`Timed out waiting for ${this.label} device status`));
|
|
6804
|
+
}, timeoutMs);
|
|
6805
|
+
timer.unref?.();
|
|
6806
|
+
const unsubscribe = this.onDeviceStatus((nextStatus) => {
|
|
6807
|
+
status = nextStatus;
|
|
6808
|
+
resolveIfComplete();
|
|
6449
6809
|
});
|
|
6450
|
-
|
|
6810
|
+
this.deviceStatusRefreshCancels.add(cancel);
|
|
6811
|
+
controller.request("sync", {
|
|
6812
|
+
runtime,
|
|
6813
|
+
recover_approvals: false,
|
|
6814
|
+
force_device_status: true
|
|
6815
|
+
}, {
|
|
6816
|
+
timeoutMs,
|
|
6817
|
+
predicate: (message) => message.type === "sync_response"
|
|
6818
|
+
}).then((response) => {
|
|
6819
|
+
if (response.success === false) {
|
|
6820
|
+
rejectOnce(new Error(typeof response.error === "string" ? response.error : `Failed to refresh ${this.label} device status`));
|
|
6821
|
+
return;
|
|
6822
|
+
}
|
|
6823
|
+
syncAcknowledged = true;
|
|
6824
|
+
resolveIfComplete();
|
|
6825
|
+
}, (error) => {
|
|
6826
|
+
rejectOnce(error instanceof Error ? error : new Error(String(error)));
|
|
6827
|
+
});
|
|
6828
|
+
});
|
|
6829
|
+
}
|
|
6830
|
+
async listMessages(options = {}) {
|
|
6831
|
+
if (!this.initialized) {
|
|
6832
|
+
await this.initialize();
|
|
6451
6833
|
}
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
const response = await this.controller.request("set_reflection_settings", {
|
|
6455
|
-
runtime: this.runtime,
|
|
6456
|
-
settings: dreamingSettings,
|
|
6457
|
-
scope: "both"
|
|
6458
|
-
}, { predicate: (message) => message.type === "set_reflection_settings_response" });
|
|
6459
|
-
ensureSuccess(response, "Failed to update dreaming settings");
|
|
6834
|
+
if (!this.controller) {
|
|
6835
|
+
throw new Error("Session is not initialized");
|
|
6460
6836
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
await this.updateModel({
|
|
6465
|
-
...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
|
|
6466
|
-
...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
|
|
6467
|
-
});
|
|
6837
|
+
const conversationId = options.conversationId ?? this._conversationId;
|
|
6838
|
+
if (!conversationId) {
|
|
6839
|
+
throw new Error("No conversation id available for listMessages()");
|
|
6468
6840
|
}
|
|
6841
|
+
return this.controller.listMessages(conversationId, options);
|
|
6469
6842
|
}
|
|
6470
|
-
|
|
6471
|
-
if (!this.
|
|
6472
|
-
|
|
6473
|
-
const deviceStatus = deviceStatusRecord(message);
|
|
6474
|
-
if (deviceStatus) {
|
|
6475
|
-
const status = toSessionDeviceStatus(deviceStatus);
|
|
6476
|
-
if (!status)
|
|
6477
|
-
return;
|
|
6478
|
-
for (const listener of [...this.deviceStatusListeners]) {
|
|
6479
|
-
try {
|
|
6480
|
-
listener(status);
|
|
6481
|
-
} catch {}
|
|
6482
|
-
}
|
|
6483
|
-
return;
|
|
6843
|
+
async bootstrapState(options = {}) {
|
|
6844
|
+
if (!this.initialized) {
|
|
6845
|
+
await this.initialize();
|
|
6484
6846
|
}
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
this.
|
|
6491
|
-
|
|
6847
|
+
const page = await this.listMessages({
|
|
6848
|
+
limit: options.limit,
|
|
6849
|
+
order: options.order
|
|
6850
|
+
});
|
|
6851
|
+
const state = {
|
|
6852
|
+
agentId: this._agentId ?? "",
|
|
6853
|
+
conversationId: this._conversationId ?? "",
|
|
6854
|
+
model: this._model,
|
|
6855
|
+
messages: page.messages
|
|
6856
|
+
};
|
|
6857
|
+
if (this.toolNames !== undefined) {
|
|
6858
|
+
state.tools = this.toolNames;
|
|
6492
6859
|
}
|
|
6493
|
-
if (
|
|
6494
|
-
|
|
6495
|
-
return;
|
|
6860
|
+
if (page.nextBefore !== undefined) {
|
|
6861
|
+
state.nextBefore = page.nextBefore;
|
|
6496
6862
|
}
|
|
6497
|
-
|
|
6498
|
-
|
|
6863
|
+
if (page.hasMore !== undefined) {
|
|
6864
|
+
state.hasMore = page.hasMore;
|
|
6865
|
+
}
|
|
6866
|
+
return state;
|
|
6867
|
+
}
|
|
6868
|
+
close() {
|
|
6869
|
+
if (this.closed)
|
|
6499
6870
|
return;
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6871
|
+
this.closed = true;
|
|
6872
|
+
this.removeMessageHandler?.();
|
|
6873
|
+
this.removeMessageHandler = null;
|
|
6874
|
+
this.turns.close();
|
|
6875
|
+
for (const cancel of [...this.deviceStatusRefreshCancels]) {
|
|
6876
|
+
cancel(new Error(`Session closed while waiting for ${this.label} device status`));
|
|
6506
6877
|
}
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6878
|
+
this.deviceStatusRefreshCancels.clear();
|
|
6879
|
+
this.deviceStatusListeners.clear();
|
|
6880
|
+
this.controller?.close();
|
|
6881
|
+
this.controller = null;
|
|
6882
|
+
this.onCoreClose();
|
|
6883
|
+
}
|
|
6884
|
+
get agentId() {
|
|
6885
|
+
return this._agentId;
|
|
6886
|
+
}
|
|
6887
|
+
get sessionId() {
|
|
6888
|
+
return this._sessionId;
|
|
6889
|
+
}
|
|
6890
|
+
get conversationId() {
|
|
6891
|
+
return this._conversationId;
|
|
6892
|
+
}
|
|
6893
|
+
async[Symbol.asyncDispose]() {
|
|
6894
|
+
this.close();
|
|
6895
|
+
}
|
|
6896
|
+
async changeDeviceState(updates) {
|
|
6897
|
+
if (!this.initialized) {
|
|
6898
|
+
await this.initialize();
|
|
6510
6899
|
}
|
|
6511
|
-
this.
|
|
6512
|
-
|
|
6513
|
-
handleLoopStatusMessage(message) {
|
|
6514
|
-
const status = loopStatusValue(message);
|
|
6515
|
-
if (!status)
|
|
6516
|
-
return;
|
|
6517
|
-
const activeRunIds = loopStatusRunIds(message);
|
|
6518
|
-
const sdkMessage = {
|
|
6519
|
-
type: "loop_status",
|
|
6520
|
-
status,
|
|
6521
|
-
activeRunIds
|
|
6522
|
-
};
|
|
6523
|
-
this.enqueue(sdkMessage);
|
|
6524
|
-
const active = this.activeTurn;
|
|
6525
|
-
if (!active)
|
|
6526
|
-
return;
|
|
6527
|
-
for (const runId of activeRunIds)
|
|
6528
|
-
active.runIds.add(runId);
|
|
6529
|
-
const hadTurnEvidence = active.observedTurnEvidence || active.observedRequiresApprovalStop;
|
|
6530
|
-
if (!hadTurnEvidence)
|
|
6531
|
-
return;
|
|
6532
|
-
if (status === "WAITING_ON_APPROVAL") {
|
|
6533
|
-
this.completeActiveTurn({
|
|
6534
|
-
runtime: active.runtime,
|
|
6535
|
-
stopReason: "requires_approval",
|
|
6536
|
-
runIds: [...active.runIds]
|
|
6537
|
-
});
|
|
6538
|
-
return;
|
|
6900
|
+
if (!this.controller || !this.runtime) {
|
|
6901
|
+
throw new Error("Session is not initialized");
|
|
6539
6902
|
}
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
errorCode: "interrupted"
|
|
6548
|
-
});
|
|
6549
|
-
return;
|
|
6903
|
+
const payload = {};
|
|
6904
|
+
if (updates.cwd !== undefined)
|
|
6905
|
+
payload.cwd = updates.cwd;
|
|
6906
|
+
if (updates.permissionMode !== undefined) {
|
|
6907
|
+
const mode = mapPermissionMode(updates.permissionMode);
|
|
6908
|
+
if (mode !== undefined)
|
|
6909
|
+
payload.mode = mode;
|
|
6550
6910
|
}
|
|
6551
|
-
if (
|
|
6552
|
-
|
|
6553
|
-
runtime: active.runtime,
|
|
6554
|
-
stopReason: null,
|
|
6555
|
-
runIds: [...active.runIds]
|
|
6556
|
-
});
|
|
6911
|
+
if (Object.keys(payload).length === 0) {
|
|
6912
|
+
throw new Error("Invalid device state update. Expected cwd or permissionMode.");
|
|
6557
6913
|
}
|
|
6914
|
+
this.controller.send({
|
|
6915
|
+
type: "change_device_state",
|
|
6916
|
+
runtime: this.runtime,
|
|
6917
|
+
payload
|
|
6918
|
+
});
|
|
6558
6919
|
}
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
return;
|
|
6563
|
-
const messageType = streamDeltaMessageType2(delta);
|
|
6564
|
-
if (messageType === "stop_reason") {
|
|
6565
|
-
const stopReason = streamDeltaStopReason2(delta) ?? null;
|
|
6566
|
-
if (stopReason === "requires_approval") {
|
|
6567
|
-
active.observedRequiresApprovalStop = true;
|
|
6568
|
-
return;
|
|
6569
|
-
}
|
|
6570
|
-
this.completeActiveTurn({
|
|
6571
|
-
runtime: active.runtime,
|
|
6572
|
-
stopReason,
|
|
6573
|
-
runIds: [...active.runIds]
|
|
6574
|
-
});
|
|
6575
|
-
return;
|
|
6920
|
+
async updateToolset(toolsetPreference) {
|
|
6921
|
+
if (!this.initialized) {
|
|
6922
|
+
await this.initialize();
|
|
6576
6923
|
}
|
|
6577
|
-
if (
|
|
6578
|
-
|
|
6579
|
-
runtime: active.runtime,
|
|
6580
|
-
stopReason: sdkMessage.stopReason,
|
|
6581
|
-
runIds: [...active.runIds],
|
|
6582
|
-
success: false,
|
|
6583
|
-
detail: sdkMessage.errorDetail ?? sdkMessage.message,
|
|
6584
|
-
errorCode: sdkMessage.errorCode
|
|
6585
|
-
});
|
|
6924
|
+
if (!this.controller || !this.runtime) {
|
|
6925
|
+
throw new Error("Session is not initialized");
|
|
6586
6926
|
}
|
|
6927
|
+
const response = await this.controller.request("update_toolset", {
|
|
6928
|
+
runtime: this.runtime,
|
|
6929
|
+
toolset_preference: toolsetPreference
|
|
6930
|
+
}, { predicate: (message) => message.type === "update_toolset_response" });
|
|
6931
|
+
ensureSuccess(response, "Failed to update toolset");
|
|
6587
6932
|
}
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6933
|
+
async afterRuntimeInitialized() {}
|
|
6934
|
+
async beforeTurn() {}
|
|
6935
|
+
onCoreClose() {}
|
|
6936
|
+
currentOptions() {
|
|
6937
|
+
return this.mode.options;
|
|
6938
|
+
}
|
|
6939
|
+
shouldEnableMemfs(options) {
|
|
6940
|
+
return false;
|
|
6941
|
+
}
|
|
6942
|
+
enableMemfsBody() {
|
|
6943
|
+
if (!this.runtime)
|
|
6944
|
+
return {};
|
|
6945
|
+
return { agent_id: this.runtime.agent_id };
|
|
6946
|
+
}
|
|
6947
|
+
setModel(model) {
|
|
6948
|
+
this._model = model;
|
|
6949
|
+
}
|
|
6950
|
+
async resolveUpdateModelPayload(input) {
|
|
6951
|
+
if (input.reasoningEffort === undefined) {
|
|
6952
|
+
return modelPayloadWithoutReasoning(input);
|
|
6599
6953
|
}
|
|
6600
|
-
if (
|
|
6601
|
-
|
|
6602
|
-
if (!content)
|
|
6603
|
-
return null;
|
|
6604
|
-
return { type: "reasoning", content, uuid, runId };
|
|
6954
|
+
if (!this.controller) {
|
|
6955
|
+
throw new Error("Session is not initialized");
|
|
6605
6956
|
}
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6957
|
+
const catalog = await this.controller.listModels();
|
|
6958
|
+
const byId = new Map(catalog.entries.map((entry) => [entry.id, entry]));
|
|
6959
|
+
const aliases = catalog.byokProviderAliases;
|
|
6960
|
+
let baseEntry;
|
|
6961
|
+
let explicitHandle;
|
|
6962
|
+
let targetHandle;
|
|
6963
|
+
if (input.modelId !== undefined) {
|
|
6964
|
+
baseEntry = byId.get(input.modelId);
|
|
6965
|
+
explicitHandle = input.modelHandle;
|
|
6966
|
+
targetHandle = baseEntry?.handle ?? toBaseModelHandle(input.modelHandle, aliases);
|
|
6967
|
+
} else if (input.modelHandle !== undefined) {
|
|
6968
|
+
explicitHandle = input.modelHandle;
|
|
6969
|
+
targetHandle = toBaseModelHandle(input.modelHandle, aliases);
|
|
6970
|
+
} else if (input.model !== undefined) {
|
|
6971
|
+
baseEntry = byId.get(input.model);
|
|
6972
|
+
if (baseEntry) {
|
|
6973
|
+
targetHandle = baseEntry.handle;
|
|
6974
|
+
} else {
|
|
6975
|
+
explicitHandle = input.model;
|
|
6976
|
+
targetHandle = toBaseModelHandle(input.model, aliases);
|
|
6623
6977
|
}
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
type: "tool_call",
|
|
6628
|
-
toolCallId,
|
|
6629
|
-
toolName,
|
|
6630
|
-
toolInput: input,
|
|
6631
|
-
rawArguments: raw,
|
|
6632
|
-
uuid,
|
|
6633
|
-
runId
|
|
6634
|
-
};
|
|
6978
|
+
} else {
|
|
6979
|
+
explicitHandle = this._model || undefined;
|
|
6980
|
+
targetHandle = toBaseModelHandle(this._model || undefined, aliases);
|
|
6635
6981
|
}
|
|
6636
|
-
if (
|
|
6637
|
-
|
|
6638
|
-
const toolCallId = (typeof delta.tool_call_id === "string" ? delta.tool_call_id : undefined) ?? (typeof toolReturn.tool_call_id === "string" ? toolReturn.tool_call_id : undefined);
|
|
6639
|
-
if (!toolCallId)
|
|
6640
|
-
return null;
|
|
6641
|
-
const content = extractTextFromContent(delta.tool_return ?? toolReturn.tool_return ?? toolReturn.content) ?? "";
|
|
6642
|
-
const status = typeof delta.status === "string" ? delta.status : toolReturn.status;
|
|
6643
|
-
return {
|
|
6644
|
-
type: "tool_result",
|
|
6645
|
-
toolCallId,
|
|
6646
|
-
content,
|
|
6647
|
-
isError: status === "error",
|
|
6648
|
-
uuid,
|
|
6649
|
-
runId
|
|
6650
|
-
};
|
|
6982
|
+
if (!targetHandle) {
|
|
6983
|
+
throw new Error("reasoningEffort requires a current model or explicit model/modelId/modelHandle.");
|
|
6651
6984
|
}
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
const approvalConflict = isApprovalConflictSignal({
|
|
6656
|
-
detail,
|
|
6657
|
-
message: typeof delta.message === "string" ? delta.message : undefined,
|
|
6658
|
-
stopReason
|
|
6659
|
-
});
|
|
6660
|
-
return {
|
|
6661
|
-
type: "error",
|
|
6662
|
-
message: detail,
|
|
6663
|
-
errorCode: approvalConflict ? "approval_conflict" : toSdkErrorCode(stopReason),
|
|
6664
|
-
approvalConflict: approvalConflict || undefined,
|
|
6665
|
-
recoverable: approvalConflict ? true : false,
|
|
6666
|
-
errorDetail: detail,
|
|
6667
|
-
stopReason,
|
|
6668
|
-
runId
|
|
6669
|
-
};
|
|
6985
|
+
const candidates = catalog.entries.filter((entry) => entry.handle === targetHandle || entry.handle === explicitHandle);
|
|
6986
|
+
if (candidates.length === 0) {
|
|
6987
|
+
throw new Error(`reasoningEffort requires a model from listModels(); no catalog entry found for ${targetHandle}.`);
|
|
6670
6988
|
}
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
maxAttempts: typeof delta.max_attempts === "number" ? delta.max_attempts : 0,
|
|
6677
|
-
delayMs: typeof delta.delay_ms === "number" ? delta.delay_ms : 0,
|
|
6678
|
-
runId
|
|
6679
|
-
};
|
|
6989
|
+
const contextWindow = getContextWindow(baseEntry?.updateArgs) ?? getContextWindow(this._modelSettings);
|
|
6990
|
+
const scopedCandidates = sameContextCandidates(candidates, contextWindow);
|
|
6991
|
+
const matchingEntry = scopedCandidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort) ?? candidates.find((entry) => getReasoningEffort(entry) === input.reasoningEffort);
|
|
6992
|
+
if (!matchingEntry) {
|
|
6993
|
+
throw new Error(`No ${input.reasoningEffort} reasoning tier found for model ${targetHandle}.`);
|
|
6680
6994
|
}
|
|
6681
|
-
|
|
6682
|
-
|
|
6995
|
+
const payload = { model_id: matchingEntry.id };
|
|
6996
|
+
if (explicitHandle !== undefined) {
|
|
6997
|
+
payload.model_handle = explicitHandle;
|
|
6683
6998
|
}
|
|
6684
|
-
return
|
|
6685
|
-
type: "stream_event",
|
|
6686
|
-
event: delta,
|
|
6687
|
-
uuid
|
|
6688
|
-
};
|
|
6689
|
-
}
|
|
6690
|
-
resultFromTurn(turn, tracker) {
|
|
6691
|
-
const stopReason = turn.stopReason ?? (turn.success === false ? "error" : undefined);
|
|
6692
|
-
const approvalConflict = isApprovalConflictSignal({
|
|
6693
|
-
detail: turn.detail,
|
|
6694
|
-
stopReason
|
|
6695
|
-
});
|
|
6696
|
-
const success = turn.success !== undefined ? turn.success && !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "") : !approvalConflict && !FAILURE_STOP_REASONS.has(stopReason ?? "");
|
|
6697
|
-
const errorCode = approvalConflict ? "approval_conflict" : turn.errorCode ?? toSdkErrorCode(stopReason);
|
|
6698
|
-
return {
|
|
6699
|
-
type: "result",
|
|
6700
|
-
success,
|
|
6701
|
-
result: success ? tracker?.assistantText || undefined : undefined,
|
|
6702
|
-
error: success ? undefined : errorCode ?? stopReason ?? "error",
|
|
6703
|
-
errorCode: success ? undefined : errorCode ?? "error",
|
|
6704
|
-
approvalConflict: approvalConflict || undefined,
|
|
6705
|
-
recoverable: approvalConflict ? true : success ? undefined : false,
|
|
6706
|
-
errorDetail: success ? undefined : turn.detail,
|
|
6707
|
-
stopReason,
|
|
6708
|
-
durationMs: Date.now() - (tracker?.startedAt || this.activeTurnStartedAt),
|
|
6709
|
-
conversationId: turn.runtime.conversation_id,
|
|
6710
|
-
runIds: turn.runIds.length > 0 ? turn.runIds : undefined
|
|
6711
|
-
};
|
|
6999
|
+
return payload;
|
|
6712
7000
|
}
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
if (resolver) {
|
|
6716
|
-
resolver(message);
|
|
7001
|
+
async applyPostInitializeOptions() {
|
|
7002
|
+
if (!this.controller || !this.runtime)
|
|
6717
7003
|
return;
|
|
7004
|
+
const options = this.currentOptions();
|
|
7005
|
+
if (this.shouldEnableMemfs(options)) {
|
|
7006
|
+
const response = await this.controller.request("enable_memfs", this.enableMemfsBody(), {
|
|
7007
|
+
predicate: (message) => message.type === "enable_memfs_response",
|
|
7008
|
+
timeoutMs: 180000
|
|
7009
|
+
});
|
|
7010
|
+
ensureSuccess(response, "Failed to enable memfs");
|
|
7011
|
+
}
|
|
7012
|
+
const dreamingSettings = resolveDreamingSettings(options.dreaming);
|
|
7013
|
+
if (dreamingSettings) {
|
|
7014
|
+
const response = await this.controller.request("set_reflection_settings", {
|
|
7015
|
+
runtime: this.runtime,
|
|
7016
|
+
settings: dreamingSettings,
|
|
7017
|
+
scope: "both"
|
|
7018
|
+
}, { predicate: (message) => message.type === "set_reflection_settings_response" });
|
|
7019
|
+
ensureSuccess(response, "Failed to update dreaming settings");
|
|
7020
|
+
}
|
|
7021
|
+
if (this.mode.kind !== "session")
|
|
7022
|
+
return;
|
|
7023
|
+
if (this.mode.options.model !== undefined || this.mode.options.reasoningEffort !== undefined) {
|
|
7024
|
+
await this.applyModelUpdate({
|
|
7025
|
+
...this.mode.options.model !== undefined ? { model: this.mode.options.model } : {},
|
|
7026
|
+
...this.mode.options.reasoningEffort !== undefined ? { reasoningEffort: this.mode.options.reasoningEffort } : {}
|
|
7027
|
+
});
|
|
6718
7028
|
}
|
|
6719
|
-
this.streamQueue.push(message);
|
|
6720
|
-
}
|
|
6721
|
-
nextMessage() {
|
|
6722
|
-
const next = this.streamQueue.shift();
|
|
6723
|
-
if (next)
|
|
6724
|
-
return Promise.resolve(next);
|
|
6725
|
-
if (this.closed)
|
|
6726
|
-
return Promise.resolve(null);
|
|
6727
|
-
return new Promise((resolve) => {
|
|
6728
|
-
this.streamResolvers.push(resolve);
|
|
6729
|
-
});
|
|
6730
7029
|
}
|
|
6731
|
-
|
|
6732
|
-
for (const
|
|
6733
|
-
|
|
7030
|
+
emitDeviceStatus(status) {
|
|
7031
|
+
for (const listener of [...this.deviceStatusListeners]) {
|
|
7032
|
+
try {
|
|
7033
|
+
listener(status);
|
|
7034
|
+
} catch {}
|
|
6734
7035
|
}
|
|
6735
7036
|
}
|
|
6736
7037
|
}
|
|
@@ -7224,13 +7525,15 @@ class AppServerSession extends RemoteClientSessionCore {
|
|
|
7224
7525
|
return options.memfs !== false;
|
|
7225
7526
|
}
|
|
7226
7527
|
async initializeRuntimeController() {
|
|
7528
|
+
await this.remoteOptions.beforeConnect?.();
|
|
7227
7529
|
const url = await this.resolveAppServerUrl();
|
|
7228
|
-
|
|
7530
|
+
await releaseAppServerManagementConnections(url);
|
|
7531
|
+
const client = applyUniqueRequestIds(createAppServerClient({
|
|
7229
7532
|
url,
|
|
7230
7533
|
...this.remoteOptions.authToken !== undefined ? { authToken: this.remoteOptions.authToken } : {},
|
|
7231
7534
|
...this.remoteOptions.WebSocket ? { WebSocket: this.remoteOptions.WebSocket } : {},
|
|
7232
7535
|
...this.remoteOptions.requestTimeoutMs !== undefined ? { requestTimeoutMs: this.remoteOptions.requestTimeoutMs } : {}
|
|
7233
|
-
});
|
|
7536
|
+
}));
|
|
7234
7537
|
this.removeControlRequestHandler = registerAppServerControlRequestHandler({
|
|
7235
7538
|
client,
|
|
7236
7539
|
getRuntime: () => this.runtime,
|
|
@@ -7629,13 +7932,58 @@ class RemoteEnvironmentClient {
|
|
|
7629
7932
|
}
|
|
7630
7933
|
}
|
|
7631
7934
|
|
|
7935
|
+
// src/cloud-sandbox.ts
|
|
7936
|
+
var MIN_TTL_MINUTES = 1;
|
|
7937
|
+
var MAX_TTL_MINUTES = 60;
|
|
7938
|
+
var MAX_GITHUB_REPOSITORIES = 10;
|
|
7939
|
+
var GITHUB_OWNER_PATTERN = /^[A-Za-z0-9-]+$/;
|
|
7940
|
+
var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7941
|
+
function validatePositiveInteger(value, name) {
|
|
7942
|
+
if (value !== undefined && (!Number.isInteger(value) || value <= 0)) {
|
|
7943
|
+
throw new Error(`Invalid ${name}. Expected a positive integer.`);
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
function validateCloudSandboxOptions(options, name) {
|
|
7947
|
+
if (options === undefined)
|
|
7948
|
+
return;
|
|
7949
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
7950
|
+
throw new Error(`Invalid ${name}. Expected an object.`);
|
|
7951
|
+
}
|
|
7952
|
+
if (options.ttlMinutes !== undefined && (!Number.isInteger(options.ttlMinutes) || options.ttlMinutes < MIN_TTL_MINUTES || options.ttlMinutes > MAX_TTL_MINUTES)) {
|
|
7953
|
+
throw new Error(`Invalid ${name}.ttlMinutes. Expected an integer between ${MIN_TTL_MINUTES} and ${MAX_TTL_MINUTES}.`);
|
|
7954
|
+
}
|
|
7955
|
+
validatePositiveInteger(options.readyTimeoutMs, `${name}.readyTimeoutMs`);
|
|
7956
|
+
validatePositiveInteger(options.readyPollIntervalMs, `${name}.readyPollIntervalMs`);
|
|
7957
|
+
validatePositiveInteger(options.refreshIntervalMs, `${name}.refreshIntervalMs`);
|
|
7958
|
+
if (options.githubRepositories !== undefined) {
|
|
7959
|
+
if (!Array.isArray(options.githubRepositories)) {
|
|
7960
|
+
throw new Error(`Invalid ${name}.githubRepositories. Expected an array.`);
|
|
7961
|
+
}
|
|
7962
|
+
if (options.githubRepositories.length > MAX_GITHUB_REPOSITORIES) {
|
|
7963
|
+
throw new Error(`Invalid ${name}.githubRepositories. Expected at most ${MAX_GITHUB_REPOSITORIES} repositories.`);
|
|
7964
|
+
}
|
|
7965
|
+
for (const [index, repository] of options.githubRepositories.entries()) {
|
|
7966
|
+
if (repository === null || typeof repository !== "object" || Array.isArray(repository)) {
|
|
7967
|
+
throw new Error(`Invalid ${name}.githubRepositories[${index}]. Expected an object.`);
|
|
7968
|
+
}
|
|
7969
|
+
if (typeof repository.owner !== "string" || !GITHUB_OWNER_PATTERN.test(repository.owner)) {
|
|
7970
|
+
throw new Error(`Invalid ${name}.githubRepositories[${index}].owner.`);
|
|
7971
|
+
}
|
|
7972
|
+
if (typeof repository.repo !== "string" || !GITHUB_REPOSITORY_PATTERN.test(repository.repo)) {
|
|
7973
|
+
throw new Error(`Invalid ${name}.githubRepositories[${index}].repo.`);
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
if (options.terminateOnClose !== undefined && typeof options.terminateOnClose !== "boolean") {
|
|
7978
|
+
throw new Error(`Invalid ${name}.terminateOnClose. Expected a boolean.`);
|
|
7979
|
+
}
|
|
7980
|
+
}
|
|
7981
|
+
|
|
7632
7982
|
// src/cloud-session.ts
|
|
7633
7983
|
var DEFAULT_CLOUD_API_BASE_URL3 = "https://api.letta.com";
|
|
7634
7984
|
var DEFAULT_TURN_TIMEOUT_MS = 120000;
|
|
7635
7985
|
var DEFAULT_PING_INTERVAL_MS = 30000;
|
|
7636
7986
|
var DEFAULT_SANDBOX_TTL_MINUTES = 5;
|
|
7637
|
-
var MIN_SANDBOX_TTL_MINUTES = 1;
|
|
7638
|
-
var MAX_SANDBOX_TTL_MINUTES = 60;
|
|
7639
7987
|
var DEFAULT_SANDBOX_READY_TIMEOUT_MS = 120000;
|
|
7640
7988
|
var DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS = 1000;
|
|
7641
7989
|
var DEFAULT_REPOSITORY_ATTACH_TIMEOUT_MS = 1e4;
|
|
@@ -7746,34 +8094,13 @@ function assertOkResponse2(response, body, action, url) {
|
|
|
7746
8094
|
throw new Error(parts.join(" — "));
|
|
7747
8095
|
}
|
|
7748
8096
|
}
|
|
7749
|
-
function
|
|
8097
|
+
function validatePositiveInteger2(value, name) {
|
|
7750
8098
|
if (value !== undefined && (!Number.isInteger(value) || value <= 0)) {
|
|
7751
8099
|
throw new Error(`Invalid ${name}. Expected a positive integer.`);
|
|
7752
8100
|
}
|
|
7753
8101
|
}
|
|
7754
|
-
function validateIntegerRange(value, name, min, max) {
|
|
7755
|
-
if (value === undefined)
|
|
7756
|
-
return;
|
|
7757
|
-
if (!Number.isInteger(value) || value < min || value > max) {
|
|
7758
|
-
throw new Error(`Invalid ${name}. Expected an integer between ${min} and ${max}.`);
|
|
7759
|
-
}
|
|
7760
|
-
}
|
|
7761
|
-
function validateCloudSandboxOptions(options, name) {
|
|
7762
|
-
if (options === undefined)
|
|
7763
|
-
return;
|
|
7764
|
-
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
7765
|
-
throw new Error(`Invalid ${name}. Expected an object.`);
|
|
7766
|
-
}
|
|
7767
|
-
validateIntegerRange(options.ttlMinutes, `${name}.ttlMinutes`, MIN_SANDBOX_TTL_MINUTES, MAX_SANDBOX_TTL_MINUTES);
|
|
7768
|
-
validatePositiveInteger(options.readyTimeoutMs, `${name}.readyTimeoutMs`);
|
|
7769
|
-
validatePositiveInteger(options.readyPollIntervalMs, `${name}.readyPollIntervalMs`);
|
|
7770
|
-
validatePositiveInteger(options.refreshIntervalMs, `${name}.refreshIntervalMs`);
|
|
7771
|
-
if (options.terminateOnClose !== undefined && typeof options.terminateOnClose !== "boolean") {
|
|
7772
|
-
throw new Error(`Invalid ${name}.terminateOnClose. Expected a boolean.`);
|
|
7773
|
-
}
|
|
7774
|
-
}
|
|
7775
8102
|
function validateCloudClientOptions(options) {
|
|
7776
|
-
|
|
8103
|
+
validatePositiveInteger2(options.requestTimeoutMs, "requestTimeoutMs");
|
|
7777
8104
|
validateCloudSandboxOptions(options.sandbox, "sandbox");
|
|
7778
8105
|
if (options.environment !== undefined && options.sandbox !== undefined) {
|
|
7779
8106
|
throw new Error("Letta Cloud sessions cannot specify both environment and sandbox options.");
|
|
@@ -8101,14 +8428,14 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8101
8428
|
apiKey,
|
|
8102
8429
|
authMode: this.cloudOptions.webSocketAuth ?? "header"
|
|
8103
8430
|
});
|
|
8104
|
-
const client = createAppServerClient({
|
|
8431
|
+
const client = applyUniqueRequestIds(createAppServerClient({
|
|
8105
8432
|
url,
|
|
8106
8433
|
WebSocket: createCloudStatusWebSocketConstructor({
|
|
8107
8434
|
cloudOptions: this.cloudOptions,
|
|
8108
8435
|
runtime: resolved.runtime
|
|
8109
8436
|
}),
|
|
8110
8437
|
requestTimeoutMs: this.cloudOptions.requestTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS
|
|
8111
|
-
});
|
|
8438
|
+
}));
|
|
8112
8439
|
this.removeControlRequestHandler = registerAppServerControlRequestHandler({
|
|
8113
8440
|
client,
|
|
8114
8441
|
getRuntime: () => this.runtime,
|
|
@@ -8376,10 +8703,15 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8376
8703
|
async createManagedSandbox(agentId, conversationId) {
|
|
8377
8704
|
const fetchImpl = getFetch3(this.cloudOptions.fetch);
|
|
8378
8705
|
const baseUrl = normalizeCloudApiBaseUrl2(this.cloudOptions.apiBaseUrl);
|
|
8706
|
+
const sandboxOptions = this.resolvedSandboxOptions();
|
|
8707
|
+
const githubRepositories = sandboxOptions.githubRepositories;
|
|
8379
8708
|
const response = await fetchImpl(`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {
|
|
8380
8709
|
method: "POST",
|
|
8381
8710
|
headers: cloudHeaders2(this.cloudOptions),
|
|
8382
|
-
body: JSON.stringify(
|
|
8711
|
+
body: JSON.stringify({
|
|
8712
|
+
...conversationId ? { conversationId } : {},
|
|
8713
|
+
...githubRepositories && githubRepositories.length > 0 ? { githubRepositories } : {}
|
|
8714
|
+
})
|
|
8383
8715
|
});
|
|
8384
8716
|
const body = await parseJsonResponse3(response);
|
|
8385
8717
|
assertOkResponse2(response, body, "Cloud create managed sandbox", `${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sandboxes`);
|
|
@@ -8390,7 +8722,6 @@ class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
8390
8722
|
if (responseConversationId !== null && responseConversationId !== conversationId) {
|
|
8391
8723
|
throw new Error(`Cloud managed sandbox response conversation mismatch: expected ${conversationId ?? "none"}, got ${responseConversationId}.`);
|
|
8392
8724
|
}
|
|
8393
|
-
const sandboxOptions = this.resolvedSandboxOptions();
|
|
8394
8725
|
const ttlMinutes = sandboxOptions.ttlMinutes ?? DEFAULT_SANDBOX_TTL_MINUTES;
|
|
8395
8726
|
const readyTimeoutMs = sandboxOptions.readyTimeoutMs ?? DEFAULT_SANDBOX_READY_TIMEOUT_MS;
|
|
8396
8727
|
const readyPollIntervalMs = sandboxOptions.readyPollIntervalMs ?? DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS;
|
|
@@ -8846,6 +9177,10 @@ class LettaAgentClientBase {
|
|
|
8846
9177
|
if (requestTimeoutMs !== undefined && (!Number.isInteger(requestTimeoutMs) || requestTimeoutMs <= 0)) {
|
|
8847
9178
|
throw new Error("Invalid appServer.requestTimeoutMs. Expected a positive integer.");
|
|
8848
9179
|
}
|
|
9180
|
+
const idleLingerMs = localOptions.appServer?.idleLingerMs;
|
|
9181
|
+
if (idleLingerMs !== undefined && (!Number.isInteger(idleLingerMs) || idleLingerMs < 0)) {
|
|
9182
|
+
throw new Error("Invalid appServer.idleLingerMs. Expected a non-negative integer.");
|
|
9183
|
+
}
|
|
8849
9184
|
const startupTimeoutMs = localOptions.appServer?.startupTimeoutMs;
|
|
8850
9185
|
if (startupTimeoutMs !== undefined && (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs <= 0)) {
|
|
8851
9186
|
throw new Error("Invalid appServer.startupTimeoutMs. Expected a positive integer.");
|
|
@@ -8858,6 +9193,9 @@ class LettaAgentClientBase {
|
|
|
8858
9193
|
if (options.requestTimeoutMs !== undefined && (!Number.isInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
|
|
8859
9194
|
throw new Error("Invalid requestTimeoutMs. Expected a positive integer.");
|
|
8860
9195
|
}
|
|
9196
|
+
if (options.idleLingerMs !== undefined && (!Number.isInteger(options.idleLingerMs) || options.idleLingerMs < 0)) {
|
|
9197
|
+
throw new Error("Invalid idleLingerMs. Expected a non-negative integer.");
|
|
9198
|
+
}
|
|
8861
9199
|
}
|
|
8862
9200
|
if (this.backend === "cloud") {
|
|
8863
9201
|
validateCloudClientOptions(options);
|
|
@@ -8872,7 +9210,7 @@ class LettaAgentClientBase {
|
|
|
8872
9210
|
}
|
|
8873
9211
|
validateCreateAgentOptions(options);
|
|
8874
9212
|
if (this.backend === "remote") {
|
|
8875
|
-
const session = new AppServerSession(this.
|
|
9213
|
+
const session = new AppServerSession(this.appServerSessionOptions(), {
|
|
8876
9214
|
kind: "create-agent",
|
|
8877
9215
|
options
|
|
8878
9216
|
});
|
|
@@ -8895,7 +9233,7 @@ class LettaAgentClientBase {
|
|
|
8895
9233
|
if (!agentId) {
|
|
8896
9234
|
throw new Error("App-server createSession() requires an agent id. Call createAgent() first or pass an agent id.");
|
|
8897
9235
|
}
|
|
8898
|
-
return new AppServerSession(this.
|
|
9236
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
8899
9237
|
kind: "session",
|
|
8900
9238
|
agentId,
|
|
8901
9239
|
newConversation: true,
|
|
@@ -8921,13 +9259,13 @@ class LettaAgentClientBase {
|
|
|
8921
9259
|
validateCreateSessionOptions(sessionOptions);
|
|
8922
9260
|
if (this.backend === "remote") {
|
|
8923
9261
|
if (looksLikeConversationId(id)) {
|
|
8924
|
-
return new AppServerSession(this.
|
|
9262
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
8925
9263
|
kind: "session",
|
|
8926
9264
|
conversationId: id,
|
|
8927
9265
|
options
|
|
8928
9266
|
});
|
|
8929
9267
|
}
|
|
8930
|
-
return new AppServerSession(this.
|
|
9268
|
+
return new AppServerSession(this.appServerSessionOptions(), {
|
|
8931
9269
|
kind: "session",
|
|
8932
9270
|
agentId: id,
|
|
8933
9271
|
defaultConversation: true,
|
|
@@ -9026,6 +9364,12 @@ class LettaAgentClientBase {
|
|
|
9026
9364
|
}
|
|
9027
9365
|
return this.options;
|
|
9028
9366
|
}
|
|
9367
|
+
appServerSessionOptions() {
|
|
9368
|
+
return {
|
|
9369
|
+
...this.remoteOptions(),
|
|
9370
|
+
beforeConnect: () => this.releaseIdleManagementConnection()
|
|
9371
|
+
};
|
|
9372
|
+
}
|
|
9029
9373
|
getRepositoriesClient() {
|
|
9030
9374
|
if (this.backend !== "cloud") {
|
|
9031
9375
|
throw new Error('client.repositories is only available with backend: "cloud".');
|
|
@@ -9033,6 +9377,9 @@ class LettaAgentClientBase {
|
|
|
9033
9377
|
this.repositoriesClient ??= new RepositoriesClient(this.cloudOptions());
|
|
9034
9378
|
return this.repositoriesClient;
|
|
9035
9379
|
}
|
|
9380
|
+
async releaseIdleManagementConnection() {
|
|
9381
|
+
await this.managementTransport?.releaseIdleConnection?.();
|
|
9382
|
+
}
|
|
9036
9383
|
getManagementTransport() {
|
|
9037
9384
|
if (this.managementTransport)
|
|
9038
9385
|
return this.managementTransport;
|
|
@@ -9140,4 +9487,4 @@ export {
|
|
|
9140
9487
|
CloudManagedSandboxExpiredError
|
|
9141
9488
|
};
|
|
9142
9489
|
|
|
9143
|
-
//# debugId=
|
|
9490
|
+
//# debugId=0186899B952456A764756E2164756E21
|