@hypercli.com/sdk 2026.3.16 → 2026.3.18-1
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/README.md +16 -3
- package/dist/agent.d.ts +1 -10
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +29 -33
- package/dist/agent.js.map +1 -1
- package/dist/agents.d.ts +102 -4
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +446 -14
- package/dist/agents.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +13 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +70 -13
- package/dist/config.js.map +1 -1
- package/dist/gateway.d.ts +41 -5
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +625 -86
- package/dist/gateway.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/instances.d.ts +1 -0
- package/dist/instances.d.ts.map +1 -1
- package/dist/instances.js +1 -0
- package/dist/instances.js.map +1 -1
- package/dist/jobs.d.ts +2 -0
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +4 -1
- package/dist/jobs.js.map +1 -1
- package/package.json +2 -2
package/dist/gateway.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Protocol: OpenClaw Gateway v3
|
|
8
8
|
*/
|
|
9
9
|
import { getPublicKeyAsync, signAsync, utils as edUtils } from "@noble/ed25519";
|
|
10
|
+
import NodeWebSocket from "ws";
|
|
10
11
|
class GatewayRequestError extends Error {
|
|
11
12
|
gatewayCode;
|
|
12
13
|
details;
|
|
@@ -21,8 +22,8 @@ const PROTOCOL_VERSION = 3;
|
|
|
21
22
|
const DEFAULT_TIMEOUT = 15_000;
|
|
22
23
|
const CHAT_TIMEOUT = 120_000;
|
|
23
24
|
const RECONNECT_CLOSE_CODE = 4008;
|
|
24
|
-
const DEFAULT_CLIENT_ID = "
|
|
25
|
-
const DEFAULT_CLIENT_MODE = "
|
|
25
|
+
const DEFAULT_CLIENT_ID = "cli";
|
|
26
|
+
const DEFAULT_CLIENT_MODE = "cli";
|
|
26
27
|
const DEFAULT_CLIENT_VERSION = "@hypercli/sdk";
|
|
27
28
|
const DEFAULT_CAPS = ["tool-events"];
|
|
28
29
|
const CONNECT_TIMER_MS = 750;
|
|
@@ -72,6 +73,331 @@ function asRecord(value) {
|
|
|
72
73
|
? value
|
|
73
74
|
: null;
|
|
74
75
|
}
|
|
76
|
+
function asContentItems(value) {
|
|
77
|
+
if (!Array.isArray(value)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
return value
|
|
81
|
+
.map((item) => asRecord(item))
|
|
82
|
+
.filter((item) => item !== null);
|
|
83
|
+
}
|
|
84
|
+
function normalizeToolArgs(value) {
|
|
85
|
+
if (typeof value !== "string") {
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
const trimmed = value.trim();
|
|
89
|
+
if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(trimmed);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function stringifyToolResult(value) {
|
|
100
|
+
if (value == null) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return JSON.stringify(value, null, 2);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return String(value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function gatewayToolCallId(record) {
|
|
114
|
+
const direct = (typeof record.id === "string" && record.id.trim()) ||
|
|
115
|
+
(typeof record.toolCallId === "string" && record.toolCallId.trim()) ||
|
|
116
|
+
(typeof record.tool_call_id === "string" && record.tool_call_id.trim());
|
|
117
|
+
return direct || undefined;
|
|
118
|
+
}
|
|
119
|
+
function gatewayToolName(record) {
|
|
120
|
+
const direct = (typeof record.name === "string" && record.name.trim()) ||
|
|
121
|
+
(typeof record.toolName === "string" && record.toolName.trim()) ||
|
|
122
|
+
(typeof record.tool_name === "string" && record.tool_name.trim());
|
|
123
|
+
return direct || undefined;
|
|
124
|
+
}
|
|
125
|
+
function mergeGatewayToolResult(toolCalls, result) {
|
|
126
|
+
const next = [...toolCalls];
|
|
127
|
+
let index = -1;
|
|
128
|
+
for (let cursor = next.length - 1; cursor >= 0; cursor -= 1) {
|
|
129
|
+
const entry = next[cursor];
|
|
130
|
+
if (result.id && entry.id && entry.id === result.id) {
|
|
131
|
+
index = cursor;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
if (result.name && entry.name === result.name && entry.result == null) {
|
|
135
|
+
index = cursor;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (index >= 0) {
|
|
140
|
+
const current = next[index];
|
|
141
|
+
next[index] = {
|
|
142
|
+
...current,
|
|
143
|
+
...(result.id ? { id: result.id } : {}),
|
|
144
|
+
...(result.result !== undefined ? { result: result.result } : {}),
|
|
145
|
+
};
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
148
|
+
next.push(result);
|
|
149
|
+
return next;
|
|
150
|
+
}
|
|
151
|
+
export function extractGatewayChatThinking(message) {
|
|
152
|
+
const record = asRecord(message);
|
|
153
|
+
if (!record) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
const parts = asContentItems(record.content)
|
|
157
|
+
.map((item) => {
|
|
158
|
+
if (item.type !== "thinking" || typeof item.thinking !== "string") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return item.thinking.trim();
|
|
162
|
+
})
|
|
163
|
+
.filter((value) => Boolean(value));
|
|
164
|
+
return parts.join("\n");
|
|
165
|
+
}
|
|
166
|
+
export function extractGatewayChatMediaUrls(message) {
|
|
167
|
+
const record = asRecord(message);
|
|
168
|
+
if (!record) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
const mediaUrls = [];
|
|
172
|
+
for (const item of asContentItems(record.content)) {
|
|
173
|
+
if (item.type !== "image") {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const source = asRecord(item.source);
|
|
177
|
+
if (!source) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (source.type === "url" && typeof source.url === "string" && source.url.trim()) {
|
|
181
|
+
mediaUrls.push(source.url);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (source.type === "base64" && typeof source.data === "string" && source.data.trim()) {
|
|
185
|
+
const mimeType = typeof source.media_type === "string" && source.media_type.trim()
|
|
186
|
+
? source.media_type.trim()
|
|
187
|
+
: "image/png";
|
|
188
|
+
mediaUrls.push(`data:${mimeType};base64,${source.data}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (typeof record.mediaUrl === "string" && record.mediaUrl.trim()) {
|
|
192
|
+
mediaUrls.push(record.mediaUrl);
|
|
193
|
+
}
|
|
194
|
+
if (Array.isArray(record.mediaUrls)) {
|
|
195
|
+
for (const entry of record.mediaUrls) {
|
|
196
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
197
|
+
mediaUrls.push(entry);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return mediaUrls;
|
|
202
|
+
}
|
|
203
|
+
export function extractGatewayChatToolCalls(message) {
|
|
204
|
+
const record = asRecord(message);
|
|
205
|
+
if (!record) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
let toolCalls = [];
|
|
209
|
+
for (const item of asContentItems(record.content)) {
|
|
210
|
+
const kind = typeof item.type === "string" ? item.type.trim().toLowerCase() : "";
|
|
211
|
+
const name = gatewayToolName(item);
|
|
212
|
+
const id = gatewayToolCallId(item);
|
|
213
|
+
if (kind === "toolcall" ||
|
|
214
|
+
kind === "tool_call" ||
|
|
215
|
+
kind === "tooluse" ||
|
|
216
|
+
kind === "tool_use" ||
|
|
217
|
+
(name && (item.arguments !== undefined || item.args !== undefined))) {
|
|
218
|
+
toolCalls.push({
|
|
219
|
+
...(id ? { id } : {}),
|
|
220
|
+
name: name ?? "tool",
|
|
221
|
+
args: normalizeToolArgs(item.arguments ?? item.args),
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (kind === "toolresult" || kind === "tool_result") {
|
|
226
|
+
toolCalls = mergeGatewayToolResult(toolCalls, {
|
|
227
|
+
...(id ? { id } : {}),
|
|
228
|
+
name: name ?? "tool",
|
|
229
|
+
result: stringifyToolResult(item.text ?? item.content ?? item.result),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (Array.isArray(record.tool_calls)) {
|
|
234
|
+
for (const item of record.tool_calls) {
|
|
235
|
+
const tool = asRecord(item);
|
|
236
|
+
if (!tool)
|
|
237
|
+
continue;
|
|
238
|
+
const name = gatewayToolName(tool);
|
|
239
|
+
if (!name)
|
|
240
|
+
continue;
|
|
241
|
+
toolCalls.push({
|
|
242
|
+
...(gatewayToolCallId(tool) ? { id: gatewayToolCallId(tool) } : {}),
|
|
243
|
+
name,
|
|
244
|
+
args: normalizeToolArgs(tool.arguments ?? tool.args),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const topLevelToolName = gatewayToolName(record);
|
|
249
|
+
const topLevelResult = stringifyToolResult(record.result ?? record.content ?? record.text ?? record.partialResult);
|
|
250
|
+
const role = typeof record.role === "string" ? record.role.trim().toLowerCase() : "";
|
|
251
|
+
if (topLevelToolName &&
|
|
252
|
+
topLevelResult &&
|
|
253
|
+
(role === "toolresult" || role === "tool_result" || record.toolCallId || record.tool_call_id)) {
|
|
254
|
+
toolCalls = mergeGatewayToolResult(toolCalls, {
|
|
255
|
+
...(gatewayToolCallId(record) ? { id: gatewayToolCallId(record) } : {}),
|
|
256
|
+
name: topLevelToolName,
|
|
257
|
+
result: topLevelResult,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return toolCalls;
|
|
261
|
+
}
|
|
262
|
+
export function normalizeGatewayChatMessage(message) {
|
|
263
|
+
const record = asRecord(message);
|
|
264
|
+
if (!record) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const text = extractMessageText(record) ?? "";
|
|
268
|
+
const thinking = extractGatewayChatThinking(record);
|
|
269
|
+
const toolCalls = extractGatewayChatToolCalls(record);
|
|
270
|
+
const mediaUrls = extractGatewayChatMediaUrls(record);
|
|
271
|
+
const timestamp = typeof record.timestamp === "number" ? record.timestamp : undefined;
|
|
272
|
+
const role = typeof record.role === "string" && record.role.trim() ? record.role : "assistant";
|
|
273
|
+
if (!text && !thinking && toolCalls.length === 0 && mediaUrls.length === 0) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
role,
|
|
278
|
+
text,
|
|
279
|
+
thinking,
|
|
280
|
+
toolCalls,
|
|
281
|
+
mediaUrls,
|
|
282
|
+
...(timestamp !== undefined ? { timestamp } : {}),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function extractMessageText(message) {
|
|
286
|
+
if (typeof message === "string") {
|
|
287
|
+
return message;
|
|
288
|
+
}
|
|
289
|
+
const record = asRecord(message);
|
|
290
|
+
if (!record) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
const content = record.content;
|
|
294
|
+
if (typeof content === "string") {
|
|
295
|
+
return content;
|
|
296
|
+
}
|
|
297
|
+
if (Array.isArray(content)) {
|
|
298
|
+
const parts = content
|
|
299
|
+
.map((entry) => {
|
|
300
|
+
const item = asRecord(entry);
|
|
301
|
+
if (!item || item.type !== "text" || typeof item.text !== "string" || !item.text) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
return item.text;
|
|
305
|
+
})
|
|
306
|
+
.filter((value) => typeof value === "string");
|
|
307
|
+
if (parts.length > 0) {
|
|
308
|
+
return parts.join("\n");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return typeof record.text === "string" ? record.text : null;
|
|
312
|
+
}
|
|
313
|
+
function extractMessageRunId(message) {
|
|
314
|
+
const record = asRecord(message);
|
|
315
|
+
if (!record) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const directRunId = typeof record.runId === "string" ? record.runId.trim() : "";
|
|
319
|
+
if (directRunId) {
|
|
320
|
+
return directRunId;
|
|
321
|
+
}
|
|
322
|
+
const agentRunId = typeof record.agentRunId === "string" ? record.agentRunId.trim() : "";
|
|
323
|
+
if (agentRunId) {
|
|
324
|
+
return agentRunId;
|
|
325
|
+
}
|
|
326
|
+
const meta = asRecord(record.meta);
|
|
327
|
+
const metaRunId = typeof meta?.runId === "string" ? meta.runId.trim() : "";
|
|
328
|
+
return metaRunId || null;
|
|
329
|
+
}
|
|
330
|
+
function latestHistoryAssistantText(messages, acceptedRunIds) {
|
|
331
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
332
|
+
const message = asRecord(messages[index]);
|
|
333
|
+
if (!message) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const role = typeof message.role === "string" ? message.role.trim().toLowerCase() : "";
|
|
337
|
+
if (role !== "assistant") {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const messageRunId = extractMessageRunId(message);
|
|
341
|
+
if (messageRunId && acceptedRunIds.size > 0 && !acceptedRunIds.has(messageRunId)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const text = extractMessageText(message)?.trim();
|
|
345
|
+
if (text) {
|
|
346
|
+
return text;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
function streamDelta(previousText, nextText) {
|
|
352
|
+
if (!nextText) {
|
|
353
|
+
return { delta: "", nextText: previousText };
|
|
354
|
+
}
|
|
355
|
+
if (previousText && nextText.startsWith(previousText)) {
|
|
356
|
+
return { delta: nextText.slice(previousText.length), nextText };
|
|
357
|
+
}
|
|
358
|
+
if (nextText === previousText) {
|
|
359
|
+
return { delta: "", nextText: previousText };
|
|
360
|
+
}
|
|
361
|
+
return { delta: nextText, nextText };
|
|
362
|
+
}
|
|
363
|
+
function parseAgentSessionKey(sessionKey) {
|
|
364
|
+
const normalized = (sessionKey ?? "").trim().toLowerCase();
|
|
365
|
+
if (!normalized) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const parts = normalized.split(":").filter(Boolean);
|
|
369
|
+
if (parts.length < 3 || parts[0] !== "agent") {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const agentId = parts[1]?.trim();
|
|
373
|
+
const rest = parts.slice(2).join(":").trim();
|
|
374
|
+
if (!agentId || !rest) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
return { agentId, rest };
|
|
378
|
+
}
|
|
379
|
+
function sameSessionKey(left, right) {
|
|
380
|
+
const normalizedLeft = (left ?? "").trim().toLowerCase();
|
|
381
|
+
const normalizedRight = (right ?? "").trim().toLowerCase();
|
|
382
|
+
if (!normalizedLeft || !normalizedRight) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
if (normalizedLeft === normalizedRight) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
const parsedLeft = parseAgentSessionKey(normalizedLeft);
|
|
389
|
+
const parsedRight = parseAgentSessionKey(normalizedRight);
|
|
390
|
+
if (parsedLeft && parsedRight) {
|
|
391
|
+
return parsedLeft.agentId === parsedRight.agentId && parsedLeft.rest === parsedRight.rest;
|
|
392
|
+
}
|
|
393
|
+
if (parsedLeft) {
|
|
394
|
+
return parsedLeft.rest === normalizedRight;
|
|
395
|
+
}
|
|
396
|
+
if (parsedRight) {
|
|
397
|
+
return normalizedLeft === parsedRight.rest;
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
75
401
|
function splitConfigPath(path) {
|
|
76
402
|
return path
|
|
77
403
|
.split(".")
|
|
@@ -641,7 +967,12 @@ export class GatewayClient {
|
|
|
641
967
|
this.caps = Array.isArray(options.caps)
|
|
642
968
|
? options.caps.map((cap) => cap.trim()).filter(Boolean)
|
|
643
969
|
: [...DEFAULT_CAPS];
|
|
644
|
-
|
|
970
|
+
// Non-browser SDK clients should not send Origin by default. OpenClaw
|
|
971
|
+
// treats any Origin header as browser-originated and applies browser
|
|
972
|
+
// origin checks to the connection.
|
|
973
|
+
this.origin = typeof options.origin === "string" && options.origin.trim()
|
|
974
|
+
? options.origin.trim()
|
|
975
|
+
: undefined;
|
|
645
976
|
this.defaultTimeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
646
977
|
this.onHello = options.onHello;
|
|
647
978
|
this.onClose = options.onClose;
|
|
@@ -766,7 +1097,8 @@ export class GatewayClient {
|
|
|
766
1097
|
openSocket() {
|
|
767
1098
|
if (this.closed || this.ws)
|
|
768
1099
|
return;
|
|
769
|
-
|
|
1100
|
+
const useBrowserSocket = "localStorage" in globalThis && typeof WebSocket !== "undefined";
|
|
1101
|
+
if (!useBrowserSocket && typeof NodeWebSocket === "undefined") {
|
|
770
1102
|
throw new Error("WebSocket is not available in this environment");
|
|
771
1103
|
}
|
|
772
1104
|
if (this.reconnectTimer) {
|
|
@@ -776,20 +1108,38 @@ export class GatewayClient {
|
|
|
776
1108
|
const wsUrl = this.token
|
|
777
1109
|
? `${this.url}${this.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.token)}`
|
|
778
1110
|
: this.url;
|
|
779
|
-
const ws =
|
|
1111
|
+
const ws = useBrowserSocket
|
|
1112
|
+
? new WebSocket(wsUrl)
|
|
1113
|
+
: new NodeWebSocket(wsUrl, this.origin ? { headers: { Origin: this.origin } } : undefined);
|
|
780
1114
|
this.ws = ws;
|
|
781
|
-
|
|
1115
|
+
if (useBrowserSocket) {
|
|
1116
|
+
ws.onopen = () => {
|
|
1117
|
+
this.queueConnect();
|
|
1118
|
+
};
|
|
1119
|
+
ws.onmessage = (event) => {
|
|
1120
|
+
this.handleMessage(String(event.data ?? ""));
|
|
1121
|
+
};
|
|
1122
|
+
ws.onerror = () => {
|
|
1123
|
+
// Close handling covers retries and surfaced errors.
|
|
1124
|
+
};
|
|
1125
|
+
ws.onclose = (event) => {
|
|
1126
|
+
this.handleClose(ws, event.code ?? 1006, String(event.reason ?? ""));
|
|
1127
|
+
};
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const nodeWs = ws;
|
|
1131
|
+
nodeWs.on("open", () => {
|
|
782
1132
|
this.queueConnect();
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
this.handleMessage(
|
|
786
|
-
};
|
|
787
|
-
|
|
1133
|
+
});
|
|
1134
|
+
nodeWs.on("message", (data) => {
|
|
1135
|
+
this.handleMessage(typeof data === "string" ? data : data.toString());
|
|
1136
|
+
});
|
|
1137
|
+
nodeWs.on("error", () => {
|
|
788
1138
|
// Close handling covers retries and surfaced errors.
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
this.handleClose(ws,
|
|
792
|
-
};
|
|
1139
|
+
});
|
|
1140
|
+
nodeWs.on("close", (code, reason) => {
|
|
1141
|
+
this.handleClose(ws, code ?? 1006, reason?.toString() ?? "");
|
|
1142
|
+
});
|
|
793
1143
|
}
|
|
794
1144
|
queueConnect() {
|
|
795
1145
|
this.connectNonce = null;
|
|
@@ -1112,15 +1462,89 @@ export class GatewayClient {
|
|
|
1112
1462
|
return normalizeOpenClawConfigSchema(res) ?? { schema: {}, uiHints: {} };
|
|
1113
1463
|
}
|
|
1114
1464
|
async configPatch(patch) {
|
|
1115
|
-
await this.rpc("config.
|
|
1465
|
+
const { hash, baseHash } = await this.rpc("config.get");
|
|
1466
|
+
await this.rpc("config.patch", {
|
|
1467
|
+
raw: JSON.stringify(patch),
|
|
1468
|
+
baseHash: hash ?? baseHash ?? "",
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
async configApply(config) {
|
|
1472
|
+
const { hash, baseHash } = await this.rpc("config.get");
|
|
1473
|
+
await this.rpc("config.apply", {
|
|
1474
|
+
raw: JSON.stringify(config),
|
|
1475
|
+
baseHash: hash ?? baseHash ?? "",
|
|
1476
|
+
});
|
|
1116
1477
|
}
|
|
1117
1478
|
async configSet(config) {
|
|
1118
|
-
await this.rpc("config.
|
|
1479
|
+
const { hash, baseHash } = await this.rpc("config.get");
|
|
1480
|
+
await this.rpc("config.set", {
|
|
1481
|
+
raw: JSON.stringify(config),
|
|
1482
|
+
baseHash: hash ?? baseHash ?? "",
|
|
1483
|
+
});
|
|
1119
1484
|
}
|
|
1120
1485
|
async modelsList() {
|
|
1121
1486
|
const res = await this.rpc("models.list");
|
|
1122
1487
|
return res?.models ?? res ?? [];
|
|
1123
1488
|
}
|
|
1489
|
+
async waitReady(timeoutMs = 300_000, options = {}) {
|
|
1490
|
+
const retryIntervalMs = options.retryIntervalMs ?? 5_000;
|
|
1491
|
+
const probe = options.probe ?? "config";
|
|
1492
|
+
const deadline = Date.now() + timeoutMs;
|
|
1493
|
+
let lastError = null;
|
|
1494
|
+
while (!this.closed) {
|
|
1495
|
+
try {
|
|
1496
|
+
if (!this.connected) {
|
|
1497
|
+
await this.connect();
|
|
1498
|
+
}
|
|
1499
|
+
if (probe === "status") {
|
|
1500
|
+
return await this.status();
|
|
1501
|
+
}
|
|
1502
|
+
return await this.configGet();
|
|
1503
|
+
}
|
|
1504
|
+
catch (error) {
|
|
1505
|
+
lastError = error;
|
|
1506
|
+
this.close();
|
|
1507
|
+
if (Date.now() >= deadline) {
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
1514
|
+
throw new Error(`Gateway readiness probe timed out after ${timeoutMs}ms${detail}`);
|
|
1515
|
+
}
|
|
1516
|
+
async channelsStatus(probe = false, timeoutMs) {
|
|
1517
|
+
const params = { probe };
|
|
1518
|
+
if (timeoutMs !== undefined)
|
|
1519
|
+
params.timeoutMs = timeoutMs;
|
|
1520
|
+
return await this.rpc("channels.status", params);
|
|
1521
|
+
}
|
|
1522
|
+
async channelsLogout(channel, accountId) {
|
|
1523
|
+
const params = { channel };
|
|
1524
|
+
if (accountId)
|
|
1525
|
+
params.accountId = accountId;
|
|
1526
|
+
return await this.rpc("channels.logout", params);
|
|
1527
|
+
}
|
|
1528
|
+
async webLoginStart(options = {}) {
|
|
1529
|
+
const params = {};
|
|
1530
|
+
if (options.force)
|
|
1531
|
+
params.force = true;
|
|
1532
|
+
if (options.timeoutMs !== undefined)
|
|
1533
|
+
params.timeoutMs = options.timeoutMs;
|
|
1534
|
+
if (options.verbose)
|
|
1535
|
+
params.verbose = true;
|
|
1536
|
+
if (options.accountId)
|
|
1537
|
+
params.accountId = options.accountId;
|
|
1538
|
+
return await this.rpc("web.login.start", params, 30_000);
|
|
1539
|
+
}
|
|
1540
|
+
async webLoginWait(options = {}) {
|
|
1541
|
+
const params = {};
|
|
1542
|
+
if (options.timeoutMs !== undefined)
|
|
1543
|
+
params.timeoutMs = options.timeoutMs;
|
|
1544
|
+
if (options.accountId)
|
|
1545
|
+
params.accountId = options.accountId;
|
|
1546
|
+
return await this.rpc("web.login.wait", params, CHAT_TIMEOUT);
|
|
1547
|
+
}
|
|
1124
1548
|
// ---------------------------------------------------------------------------
|
|
1125
1549
|
// Sessions
|
|
1126
1550
|
// ---------------------------------------------------------------------------
|
|
@@ -1148,6 +1572,7 @@ export class GatewayClient {
|
|
|
1148
1572
|
async sendChat(message, sessionKey = "main", agentId, attachments) {
|
|
1149
1573
|
const params = {
|
|
1150
1574
|
message,
|
|
1575
|
+
deliver: false,
|
|
1151
1576
|
sessionKey,
|
|
1152
1577
|
idempotencyKey: makeId(),
|
|
1153
1578
|
};
|
|
@@ -1167,90 +1592,204 @@ export class GatewayClient {
|
|
|
1167
1592
|
if (!this.connected || !this.ws) {
|
|
1168
1593
|
throw new Error("Not connected");
|
|
1169
1594
|
}
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
id,
|
|
1174
|
-
method: "chat.send",
|
|
1175
|
-
params: {
|
|
1176
|
-
message,
|
|
1177
|
-
sessionKey,
|
|
1178
|
-
idempotencyKey: makeId(),
|
|
1179
|
-
},
|
|
1180
|
-
};
|
|
1181
|
-
const events = [];
|
|
1595
|
+
const idempotencyKey = makeId();
|
|
1596
|
+
const acceptedRunIds = new Set([idempotencyKey]);
|
|
1597
|
+
const queuedEvents = [];
|
|
1182
1598
|
let resolveWait = null;
|
|
1183
|
-
let
|
|
1184
|
-
let
|
|
1185
|
-
|
|
1599
|
+
let streamedDisplayText = false;
|
|
1600
|
+
let lastLegacyText = "";
|
|
1601
|
+
let lastThinkingText = "";
|
|
1602
|
+
const seenToolCallIds = new Set();
|
|
1603
|
+
const seenToolResultIds = new Set();
|
|
1186
1604
|
const handler = (evt) => {
|
|
1187
|
-
if (evt.event?.startsWith("chat.")) {
|
|
1188
|
-
|
|
1605
|
+
if (evt.event === "chat" || evt.event?.startsWith("chat.")) {
|
|
1606
|
+
queuedEvents.push(evt);
|
|
1607
|
+
const waiter = resolveWait;
|
|
1608
|
+
resolveWait = null;
|
|
1609
|
+
waiter?.();
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
this.eventHandlers.add(handler);
|
|
1613
|
+
try {
|
|
1614
|
+
const ack = await this.rpc("chat.send", {
|
|
1615
|
+
message,
|
|
1616
|
+
deliver: false,
|
|
1617
|
+
sessionKey,
|
|
1618
|
+
idempotencyKey,
|
|
1619
|
+
}, CHAT_TIMEOUT);
|
|
1620
|
+
const serverRunId = typeof ack?.runId === "string" ? ack.runId.trim() : "";
|
|
1621
|
+
if (serverRunId) {
|
|
1622
|
+
acceptedRunIds.add(serverRunId);
|
|
1623
|
+
}
|
|
1624
|
+
let deadline = Date.now() + CHAT_TIMEOUT;
|
|
1625
|
+
while (Date.now() < deadline) {
|
|
1626
|
+
if (queuedEvents.length === 0) {
|
|
1627
|
+
const remainingMs = Math.max(100, Math.min(1000, deadline - Date.now()));
|
|
1628
|
+
await new Promise((resolve) => {
|
|
1629
|
+
const timer = setTimeout(() => {
|
|
1630
|
+
if (resolveWait === release) {
|
|
1631
|
+
resolveWait = null;
|
|
1632
|
+
}
|
|
1633
|
+
resolve();
|
|
1634
|
+
}, remainingMs);
|
|
1635
|
+
const release = () => {
|
|
1636
|
+
clearTimeout(timer);
|
|
1637
|
+
resolve();
|
|
1638
|
+
};
|
|
1639
|
+
resolveWait = release;
|
|
1640
|
+
});
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
const evt = queuedEvents.shift();
|
|
1644
|
+
const payload = asRecord(evt.payload) ?? {};
|
|
1645
|
+
const payloadRunId = typeof payload.runId === "string" ? payload.runId.trim() : "";
|
|
1646
|
+
const payloadSessionKey = typeof payload.sessionKey === "string" ? payload.sessionKey.trim() : "";
|
|
1647
|
+
if (payloadRunId && !acceptedRunIds.has(payloadRunId)) {
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
if (payloadSessionKey && !sameSessionKey(payloadSessionKey, sessionKey)) {
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
deadline = Date.now() + CHAT_TIMEOUT;
|
|
1189
1654
|
if (evt.event === "chat.content") {
|
|
1190
|
-
|
|
1655
|
+
const text = typeof payload.text === "string" ? payload.text : "";
|
|
1656
|
+
if (text) {
|
|
1657
|
+
streamedDisplayText = true;
|
|
1658
|
+
yield { type: "content", text };
|
|
1659
|
+
}
|
|
1660
|
+
continue;
|
|
1191
1661
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1662
|
+
if (evt.event === "chat.thinking") {
|
|
1663
|
+
const text = typeof payload.text === "string" ? payload.text : "";
|
|
1664
|
+
if (text) {
|
|
1665
|
+
lastThinkingText += text;
|
|
1666
|
+
}
|
|
1667
|
+
yield { type: "thinking", text };
|
|
1668
|
+
continue;
|
|
1194
1669
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1670
|
+
if (evt.event === "chat.tool_call") {
|
|
1671
|
+
const toolCallId = typeof payload.toolCallId === "string" && payload.toolCallId.trim()
|
|
1672
|
+
? payload.toolCallId.trim()
|
|
1673
|
+
: `${typeof payload.name === "string" ? payload.name : "tool"}:${JSON.stringify(payload.args ?? payload.arguments ?? null)}`;
|
|
1674
|
+
seenToolCallIds.add(toolCallId);
|
|
1675
|
+
yield { type: "tool_call", data: payload };
|
|
1676
|
+
continue;
|
|
1197
1677
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1678
|
+
if (evt.event === "chat.tool_result") {
|
|
1679
|
+
const toolCallId = typeof payload.toolCallId === "string" && payload.toolCallId.trim()
|
|
1680
|
+
? payload.toolCallId.trim()
|
|
1681
|
+
: `${typeof payload.name === "string" ? payload.name : "tool"}:${JSON.stringify(payload.args ?? payload.arguments ?? null)}`;
|
|
1682
|
+
seenToolResultIds.add(toolCallId);
|
|
1683
|
+
yield { type: "tool_result", data: payload };
|
|
1684
|
+
continue;
|
|
1200
1685
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1686
|
+
if (evt.event === "chat.done") {
|
|
1687
|
+
yield { type: "done", data: payload };
|
|
1688
|
+
return;
|
|
1204
1689
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1690
|
+
if (evt.event === "chat.error") {
|
|
1691
|
+
yield {
|
|
1692
|
+
type: "error",
|
|
1693
|
+
text: typeof payload.message === "string" ? payload.message : "Unknown error",
|
|
1694
|
+
data: payload,
|
|
1695
|
+
};
|
|
1696
|
+
return;
|
|
1208
1697
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
};
|
|
1212
|
-
this.eventHandlers.add(handler);
|
|
1213
|
-
// Also listen for the RPC response
|
|
1214
|
-
const timer = setTimeout(() => {
|
|
1215
|
-
this.pending.delete(id);
|
|
1216
|
-
error = new Error("Chat timeout");
|
|
1217
|
-
done = true;
|
|
1218
|
-
resolveWait?.();
|
|
1219
|
-
}, CHAT_TIMEOUT);
|
|
1220
|
-
this.pending.set(id, {
|
|
1221
|
-
resolve: () => {
|
|
1222
|
-
clearTimeout(timer);
|
|
1223
|
-
done = true;
|
|
1224
|
-
resolveWait?.();
|
|
1225
|
-
},
|
|
1226
|
-
reject: (rejection) => {
|
|
1227
|
-
clearTimeout(timer);
|
|
1228
|
-
error = rejection instanceof Error ? rejection : new Error(String(rejection));
|
|
1229
|
-
done = true;
|
|
1230
|
-
resolveWait?.();
|
|
1231
|
-
},
|
|
1232
|
-
timer,
|
|
1233
|
-
});
|
|
1234
|
-
this.ws.send(JSON.stringify(req));
|
|
1235
|
-
try {
|
|
1236
|
-
while (!done || events.length > 0) {
|
|
1237
|
-
if (events.length > 0) {
|
|
1238
|
-
yield events.shift();
|
|
1698
|
+
if (evt.event !== "chat") {
|
|
1699
|
+
continue;
|
|
1239
1700
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1701
|
+
const state = typeof payload.state === "string" ? payload.state.trim().toLowerCase() : "";
|
|
1702
|
+
const currentText = extractMessageText(payload.message) ?? "";
|
|
1703
|
+
const normalizedMessage = normalizeGatewayChatMessage(payload.message);
|
|
1704
|
+
if (state === "delta") {
|
|
1705
|
+
const streamed = streamDelta(lastLegacyText, currentText);
|
|
1706
|
+
lastLegacyText = streamed.nextText;
|
|
1707
|
+
if (streamed.delta) {
|
|
1708
|
+
streamedDisplayText = true;
|
|
1709
|
+
yield { type: "content", text: streamed.delta, data: payload };
|
|
1710
|
+
}
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
if (state === "final") {
|
|
1714
|
+
const thinkingDelta = normalizedMessage?.thinking
|
|
1715
|
+
? streamDelta(lastThinkingText, normalizedMessage.thinking)
|
|
1716
|
+
: { delta: "", nextText: lastThinkingText };
|
|
1717
|
+
lastThinkingText = thinkingDelta.nextText;
|
|
1718
|
+
if (thinkingDelta.delta) {
|
|
1719
|
+
yield { type: "thinking", text: thinkingDelta.delta, data: payload };
|
|
1720
|
+
}
|
|
1721
|
+
for (const toolCall of normalizedMessage?.toolCalls ?? []) {
|
|
1722
|
+
const toolCallKey = toolCall.id?.trim() ||
|
|
1723
|
+
`${toolCall.name}:${JSON.stringify(toolCall.args ?? null)}`;
|
|
1724
|
+
if (toolCall.args !== undefined && !seenToolCallIds.has(toolCallKey)) {
|
|
1725
|
+
seenToolCallIds.add(toolCallKey);
|
|
1726
|
+
yield {
|
|
1727
|
+
type: "tool_call",
|
|
1728
|
+
data: {
|
|
1729
|
+
...(toolCall.id ? { toolCallId: toolCall.id } : {}),
|
|
1730
|
+
name: toolCall.name,
|
|
1731
|
+
args: toolCall.args,
|
|
1732
|
+
},
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
if (toolCall.result !== undefined && !seenToolResultIds.has(toolCallKey)) {
|
|
1736
|
+
seenToolResultIds.add(toolCallKey);
|
|
1737
|
+
yield {
|
|
1738
|
+
type: "tool_result",
|
|
1739
|
+
data: {
|
|
1740
|
+
...(toolCall.id ? { toolCallId: toolCall.id } : {}),
|
|
1741
|
+
name: toolCall.name,
|
|
1742
|
+
result: toolCall.result,
|
|
1743
|
+
},
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (currentText) {
|
|
1748
|
+
const streamed = streamDelta(lastLegacyText, currentText);
|
|
1749
|
+
lastLegacyText = streamed.nextText;
|
|
1750
|
+
if (streamed.delta) {
|
|
1751
|
+
streamedDisplayText = true;
|
|
1752
|
+
yield { type: "content", text: streamed.delta, data: payload };
|
|
1753
|
+
}
|
|
1754
|
+
yield { type: "done", data: payload };
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (streamedDisplayText || lastLegacyText) {
|
|
1758
|
+
yield { type: "done", data: payload };
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (normalizedMessage?.thinking || (normalizedMessage?.toolCalls.length ?? 0) > 0) {
|
|
1762
|
+
yield { type: "done", data: payload };
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const historyText = latestHistoryAssistantText(await this.chatHistory(sessionKey, 20), acceptedRunIds);
|
|
1766
|
+
if (historyText) {
|
|
1767
|
+
yield { type: "content", text: historyText, data: payload };
|
|
1768
|
+
}
|
|
1769
|
+
yield { type: "done", data: payload };
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
if (state === "error" || state === "aborted") {
|
|
1773
|
+
if (currentText) {
|
|
1774
|
+
const streamed = streamDelta(lastLegacyText, currentText);
|
|
1775
|
+
lastLegacyText = streamed.nextText;
|
|
1776
|
+
if (streamed.delta) {
|
|
1777
|
+
streamedDisplayText = true;
|
|
1778
|
+
yield { type: "content", text: streamed.delta, data: payload };
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
yield {
|
|
1782
|
+
type: "error",
|
|
1783
|
+
text: typeof payload.errorMessage === "string" ? payload.errorMessage : state,
|
|
1784
|
+
data: payload,
|
|
1785
|
+
};
|
|
1786
|
+
return;
|
|
1245
1787
|
}
|
|
1246
1788
|
}
|
|
1247
|
-
|
|
1248
|
-
throw error;
|
|
1789
|
+
throw new Error("Streaming chat.send timed out");
|
|
1249
1790
|
}
|
|
1250
1791
|
finally {
|
|
1251
|
-
clearTimeout(timer);
|
|
1252
1792
|
this.eventHandlers.delete(handler);
|
|
1253
|
-
this.pending.delete(id);
|
|
1254
1793
|
}
|
|
1255
1794
|
}
|
|
1256
1795
|
// ---------------------------------------------------------------------------
|