@hypercli.com/sdk 2026.3.18 → 2026.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/gateway.js CHANGED
@@ -73,6 +73,243 @@ function asRecord(value) {
73
73
  ? value
74
74
  : null;
75
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 isBrowserChatAttachment(attachment) {
85
+ return typeof attachment.dataUrl === "string";
86
+ }
87
+ function parseAttachmentDataUrl(dataUrl) {
88
+ const match = /^data:([^;]+);base64,(.+)$/i.exec(dataUrl.trim());
89
+ if (!match)
90
+ return null;
91
+ return { mimeType: match[1], content: match[2] };
92
+ }
93
+ export function normalizeChatAttachments(attachments) {
94
+ if (!attachments || attachments.length === 0)
95
+ return undefined;
96
+ return attachments.map((attachment) => {
97
+ if (!isBrowserChatAttachment(attachment)) {
98
+ return attachment;
99
+ }
100
+ const parsed = parseAttachmentDataUrl(attachment.dataUrl);
101
+ if (!parsed) {
102
+ throw new Error(`Invalid chat attachment dataUrl for mime type ${attachment.mimeType}`);
103
+ }
104
+ return {
105
+ type: "image",
106
+ mimeType: parsed.mimeType,
107
+ content: parsed.content,
108
+ ...(attachment.fileName ? { fileName: attachment.fileName } : {}),
109
+ };
110
+ });
111
+ }
112
+ function normalizeToolArgs(value) {
113
+ if (typeof value !== "string") {
114
+ return value;
115
+ }
116
+ const trimmed = value.trim();
117
+ if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {
118
+ return value;
119
+ }
120
+ try {
121
+ return JSON.parse(trimmed);
122
+ }
123
+ catch {
124
+ return value;
125
+ }
126
+ }
127
+ function stringifyToolResult(value) {
128
+ if (value == null) {
129
+ return undefined;
130
+ }
131
+ if (typeof value === "string") {
132
+ return value;
133
+ }
134
+ try {
135
+ return JSON.stringify(value, null, 2);
136
+ }
137
+ catch {
138
+ return String(value);
139
+ }
140
+ }
141
+ function gatewayToolCallId(record) {
142
+ const direct = (typeof record.id === "string" && record.id.trim()) ||
143
+ (typeof record.toolCallId === "string" && record.toolCallId.trim()) ||
144
+ (typeof record.tool_call_id === "string" && record.tool_call_id.trim());
145
+ return direct || undefined;
146
+ }
147
+ function gatewayToolName(record) {
148
+ const direct = (typeof record.name === "string" && record.name.trim()) ||
149
+ (typeof record.toolName === "string" && record.toolName.trim()) ||
150
+ (typeof record.tool_name === "string" && record.tool_name.trim());
151
+ return direct || undefined;
152
+ }
153
+ function mergeGatewayToolResult(toolCalls, result) {
154
+ const next = [...toolCalls];
155
+ let index = -1;
156
+ for (let cursor = next.length - 1; cursor >= 0; cursor -= 1) {
157
+ const entry = next[cursor];
158
+ if (result.id && entry.id && entry.id === result.id) {
159
+ index = cursor;
160
+ break;
161
+ }
162
+ if (result.name && entry.name === result.name && entry.result == null) {
163
+ index = cursor;
164
+ break;
165
+ }
166
+ }
167
+ if (index >= 0) {
168
+ const current = next[index];
169
+ next[index] = {
170
+ ...current,
171
+ ...(result.id ? { id: result.id } : {}),
172
+ ...(result.result !== undefined ? { result: result.result } : {}),
173
+ };
174
+ return next;
175
+ }
176
+ next.push(result);
177
+ return next;
178
+ }
179
+ export function extractGatewayChatThinking(message) {
180
+ const record = asRecord(message);
181
+ if (!record) {
182
+ return "";
183
+ }
184
+ const parts = asContentItems(record.content)
185
+ .map((item) => {
186
+ if (item.type !== "thinking" || typeof item.thinking !== "string") {
187
+ return null;
188
+ }
189
+ return item.thinking.trim();
190
+ })
191
+ .filter((value) => Boolean(value));
192
+ return parts.join("\n");
193
+ }
194
+ export function extractGatewayChatMediaUrls(message) {
195
+ const record = asRecord(message);
196
+ if (!record) {
197
+ return [];
198
+ }
199
+ const mediaUrls = [];
200
+ for (const item of asContentItems(record.content)) {
201
+ if (item.type !== "image") {
202
+ continue;
203
+ }
204
+ const source = asRecord(item.source);
205
+ if (!source) {
206
+ continue;
207
+ }
208
+ if (source.type === "url" && typeof source.url === "string" && source.url.trim()) {
209
+ mediaUrls.push(source.url);
210
+ continue;
211
+ }
212
+ if (source.type === "base64" && typeof source.data === "string" && source.data.trim()) {
213
+ const mimeType = typeof source.media_type === "string" && source.media_type.trim()
214
+ ? source.media_type.trim()
215
+ : "image/png";
216
+ mediaUrls.push(`data:${mimeType};base64,${source.data}`);
217
+ }
218
+ }
219
+ if (typeof record.mediaUrl === "string" && record.mediaUrl.trim()) {
220
+ mediaUrls.push(record.mediaUrl);
221
+ }
222
+ if (Array.isArray(record.mediaUrls)) {
223
+ for (const entry of record.mediaUrls) {
224
+ if (typeof entry === "string" && entry.trim()) {
225
+ mediaUrls.push(entry);
226
+ }
227
+ }
228
+ }
229
+ return mediaUrls;
230
+ }
231
+ export function extractGatewayChatToolCalls(message) {
232
+ const record = asRecord(message);
233
+ if (!record) {
234
+ return [];
235
+ }
236
+ let toolCalls = [];
237
+ for (const item of asContentItems(record.content)) {
238
+ const kind = typeof item.type === "string" ? item.type.trim().toLowerCase() : "";
239
+ const name = gatewayToolName(item);
240
+ const id = gatewayToolCallId(item);
241
+ if (kind === "toolcall" ||
242
+ kind === "tool_call" ||
243
+ kind === "tooluse" ||
244
+ kind === "tool_use" ||
245
+ (name && (item.arguments !== undefined || item.args !== undefined))) {
246
+ toolCalls.push({
247
+ ...(id ? { id } : {}),
248
+ name: name ?? "tool",
249
+ args: normalizeToolArgs(item.arguments ?? item.args),
250
+ });
251
+ continue;
252
+ }
253
+ if (kind === "toolresult" || kind === "tool_result") {
254
+ toolCalls = mergeGatewayToolResult(toolCalls, {
255
+ ...(id ? { id } : {}),
256
+ name: name ?? "tool",
257
+ result: stringifyToolResult(item.text ?? item.content ?? item.result),
258
+ });
259
+ }
260
+ }
261
+ if (Array.isArray(record.tool_calls)) {
262
+ for (const item of record.tool_calls) {
263
+ const tool = asRecord(item);
264
+ if (!tool)
265
+ continue;
266
+ const name = gatewayToolName(tool);
267
+ if (!name)
268
+ continue;
269
+ toolCalls.push({
270
+ ...(gatewayToolCallId(tool) ? { id: gatewayToolCallId(tool) } : {}),
271
+ name,
272
+ args: normalizeToolArgs(tool.arguments ?? tool.args),
273
+ });
274
+ }
275
+ }
276
+ const topLevelToolName = gatewayToolName(record);
277
+ const topLevelResult = stringifyToolResult(record.result ?? record.content ?? record.text ?? record.partialResult);
278
+ const role = typeof record.role === "string" ? record.role.trim().toLowerCase() : "";
279
+ if (topLevelToolName &&
280
+ topLevelResult &&
281
+ (role === "toolresult" || role === "tool_result" || record.toolCallId || record.tool_call_id)) {
282
+ toolCalls = mergeGatewayToolResult(toolCalls, {
283
+ ...(gatewayToolCallId(record) ? { id: gatewayToolCallId(record) } : {}),
284
+ name: topLevelToolName,
285
+ result: topLevelResult,
286
+ });
287
+ }
288
+ return toolCalls;
289
+ }
290
+ export function normalizeGatewayChatMessage(message) {
291
+ const record = asRecord(message);
292
+ if (!record) {
293
+ return null;
294
+ }
295
+ const text = extractMessageText(record) ?? "";
296
+ const thinking = extractGatewayChatThinking(record);
297
+ const toolCalls = extractGatewayChatToolCalls(record);
298
+ const mediaUrls = extractGatewayChatMediaUrls(record);
299
+ const timestamp = typeof record.timestamp === "number" ? record.timestamp : undefined;
300
+ const role = typeof record.role === "string" && record.role.trim() ? record.role : "assistant";
301
+ if (!text && !thinking && toolCalls.length === 0 && mediaUrls.length === 0) {
302
+ return null;
303
+ }
304
+ return {
305
+ role,
306
+ text,
307
+ thinking,
308
+ toolCalls,
309
+ mediaUrls,
310
+ ...(timestamp !== undefined ? { timestamp } : {}),
311
+ };
312
+ }
76
313
  function extractMessageText(message) {
77
314
  if (typeof message === "string") {
78
315
  return message;
@@ -1344,8 +1581,11 @@ export class GatewayClient {
1344
1581
  return res?.sessions ?? res ?? [];
1345
1582
  }
1346
1583
  async sessionsPreview(sessionKey, limit = 20) {
1347
- const res = await this.rpc("sessions.preview", { sessionKey, limit });
1348
- return res?.messages ?? res ?? [];
1584
+ const res = await this.rpc("sessions.preview", { keys: [sessionKey], limit });
1585
+ return res?.previews?.[0]?.items ?? [];
1586
+ }
1587
+ async sessionsPatch(patch) {
1588
+ return await this.rpc("sessions.patch", patch);
1349
1589
  }
1350
1590
  async chatHistory(sessionKey, limit = 50) {
1351
1591
  const params = { limit };
@@ -1361,6 +1601,7 @@ export class GatewayClient {
1361
1601
  await this.rpc("chat.abort", params);
1362
1602
  }
1363
1603
  async sendChat(message, sessionKey = "main", agentId, attachments) {
1604
+ const normalizedAttachments = normalizeChatAttachments(attachments);
1364
1605
  const params = {
1365
1606
  message,
1366
1607
  deliver: false,
@@ -1369,17 +1610,20 @@ export class GatewayClient {
1369
1610
  };
1370
1611
  if (agentId)
1371
1612
  params.agentId = agentId;
1372
- if (attachments && attachments.length > 0)
1373
- params.attachments = attachments;
1613
+ if (normalizedAttachments)
1614
+ params.attachments = normalizedAttachments;
1374
1615
  return this.rpc("chat.send", params, CHAT_TIMEOUT);
1375
1616
  }
1376
- async sessionsReset(sessionKey) {
1377
- await this.rpc("sessions.reset", { sessionKey });
1617
+ async sessionsReset(sessionKey, reason) {
1618
+ const params = { key: sessionKey };
1619
+ if (reason)
1620
+ params.reason = reason;
1621
+ await this.rpc("sessions.reset", params);
1378
1622
  }
1379
1623
  // ---------------------------------------------------------------------------
1380
1624
  // Chat (streaming via events)
1381
1625
  // ---------------------------------------------------------------------------
1382
- async *chatSend(message, sessionKey) {
1626
+ async *chatSend(message, sessionKey, attachments) {
1383
1627
  if (!this.connected || !this.ws) {
1384
1628
  throw new Error("Not connected");
1385
1629
  }
@@ -1389,6 +1633,9 @@ export class GatewayClient {
1389
1633
  let resolveWait = null;
1390
1634
  let streamedDisplayText = false;
1391
1635
  let lastLegacyText = "";
1636
+ let lastThinkingText = "";
1637
+ const seenToolCallIds = new Set();
1638
+ const seenToolResultIds = new Set();
1392
1639
  const handler = (evt) => {
1393
1640
  if (evt.event === "chat" || evt.event?.startsWith("chat.")) {
1394
1641
  queuedEvents.push(evt);
@@ -1399,12 +1646,16 @@ export class GatewayClient {
1399
1646
  };
1400
1647
  this.eventHandlers.add(handler);
1401
1648
  try {
1402
- const ack = await this.rpc("chat.send", {
1649
+ const normalizedAttachments = normalizeChatAttachments(attachments);
1650
+ const params = {
1403
1651
  message,
1404
1652
  deliver: false,
1405
1653
  sessionKey,
1406
1654
  idempotencyKey,
1407
- }, CHAT_TIMEOUT);
1655
+ };
1656
+ if (normalizedAttachments)
1657
+ params.attachments = normalizedAttachments;
1658
+ const ack = await this.rpc("chat.send", params, CHAT_TIMEOUT);
1408
1659
  const serverRunId = typeof ack?.runId === "string" ? ack.runId.trim() : "";
1409
1660
  if (serverRunId) {
1410
1661
  acceptedRunIds.add(serverRunId);
@@ -1447,15 +1698,61 @@ export class GatewayClient {
1447
1698
  }
1448
1699
  continue;
1449
1700
  }
1701
+ if (evt.event === "agent" && String(payload.stream || "").toLowerCase() === "tool") {
1702
+ const toolPayload = asRecord(payload.data) ?? {};
1703
+ const phase = typeof toolPayload.phase === "string" ? toolPayload.phase.toLowerCase() : "";
1704
+ if (phase === "start") {
1705
+ const toolCallId = typeof toolPayload.toolCallId === "string" && toolPayload.toolCallId.trim()
1706
+ ? toolPayload.toolCallId.trim()
1707
+ : `${typeof toolPayload.name === "string" ? toolPayload.name : "tool"}:${JSON.stringify(toolPayload.args ?? null)}`;
1708
+ seenToolCallIds.add(toolCallId);
1709
+ yield {
1710
+ type: "tool_call",
1711
+ data: {
1712
+ ...(toolPayload.toolCallId ? { toolCallId: toolPayload.toolCallId } : {}),
1713
+ name: toolPayload.name,
1714
+ args: toolPayload.args,
1715
+ },
1716
+ };
1717
+ }
1718
+ else if (phase === "result") {
1719
+ const toolCallId = typeof toolPayload.toolCallId === "string" && toolPayload.toolCallId.trim()
1720
+ ? toolPayload.toolCallId.trim()
1721
+ : `${typeof toolPayload.name === "string" ? toolPayload.name : "tool"}:${JSON.stringify(toolPayload.args ?? null)}`;
1722
+ seenToolResultIds.add(toolCallId);
1723
+ yield {
1724
+ type: "tool_result",
1725
+ data: {
1726
+ ...(toolPayload.toolCallId ? { toolCallId: toolPayload.toolCallId } : {}),
1727
+ name: toolPayload.name,
1728
+ result: toolPayload.result,
1729
+ isError: toolPayload.isError,
1730
+ },
1731
+ };
1732
+ }
1733
+ continue;
1734
+ }
1450
1735
  if (evt.event === "chat.thinking") {
1451
- yield { type: "thinking", text: typeof payload.text === "string" ? payload.text : "" };
1736
+ const text = typeof payload.text === "string" ? payload.text : "";
1737
+ if (text) {
1738
+ lastThinkingText += text;
1739
+ }
1740
+ yield { type: "thinking", text };
1452
1741
  continue;
1453
1742
  }
1454
1743
  if (evt.event === "chat.tool_call") {
1744
+ const toolCallId = typeof payload.toolCallId === "string" && payload.toolCallId.trim()
1745
+ ? payload.toolCallId.trim()
1746
+ : `${typeof payload.name === "string" ? payload.name : "tool"}:${JSON.stringify(payload.args ?? payload.arguments ?? null)}`;
1747
+ seenToolCallIds.add(toolCallId);
1455
1748
  yield { type: "tool_call", data: payload };
1456
1749
  continue;
1457
1750
  }
1458
1751
  if (evt.event === "chat.tool_result") {
1752
+ const toolCallId = typeof payload.toolCallId === "string" && payload.toolCallId.trim()
1753
+ ? payload.toolCallId.trim()
1754
+ : `${typeof payload.name === "string" ? payload.name : "tool"}:${JSON.stringify(payload.args ?? payload.arguments ?? null)}`;
1755
+ seenToolResultIds.add(toolCallId);
1459
1756
  yield { type: "tool_result", data: payload };
1460
1757
  continue;
1461
1758
  }
@@ -1476,6 +1773,7 @@ export class GatewayClient {
1476
1773
  }
1477
1774
  const state = typeof payload.state === "string" ? payload.state.trim().toLowerCase() : "";
1478
1775
  const currentText = extractMessageText(payload.message) ?? "";
1776
+ const normalizedMessage = normalizeGatewayChatMessage(payload.message);
1479
1777
  if (state === "delta") {
1480
1778
  const streamed = streamDelta(lastLegacyText, currentText);
1481
1779
  lastLegacyText = streamed.nextText;
@@ -1486,6 +1784,39 @@ export class GatewayClient {
1486
1784
  continue;
1487
1785
  }
1488
1786
  if (state === "final") {
1787
+ const thinkingDelta = normalizedMessage?.thinking
1788
+ ? streamDelta(lastThinkingText, normalizedMessage.thinking)
1789
+ : { delta: "", nextText: lastThinkingText };
1790
+ lastThinkingText = thinkingDelta.nextText;
1791
+ if (thinkingDelta.delta) {
1792
+ yield { type: "thinking", text: thinkingDelta.delta, data: payload };
1793
+ }
1794
+ for (const toolCall of normalizedMessage?.toolCalls ?? []) {
1795
+ const toolCallKey = toolCall.id?.trim() ||
1796
+ `${toolCall.name}:${JSON.stringify(toolCall.args ?? null)}`;
1797
+ if (toolCall.args !== undefined && !seenToolCallIds.has(toolCallKey)) {
1798
+ seenToolCallIds.add(toolCallKey);
1799
+ yield {
1800
+ type: "tool_call",
1801
+ data: {
1802
+ ...(toolCall.id ? { toolCallId: toolCall.id } : {}),
1803
+ name: toolCall.name,
1804
+ args: toolCall.args,
1805
+ },
1806
+ };
1807
+ }
1808
+ if (toolCall.result !== undefined && !seenToolResultIds.has(toolCallKey)) {
1809
+ seenToolResultIds.add(toolCallKey);
1810
+ yield {
1811
+ type: "tool_result",
1812
+ data: {
1813
+ ...(toolCall.id ? { toolCallId: toolCall.id } : {}),
1814
+ name: toolCall.name,
1815
+ result: toolCall.result,
1816
+ },
1817
+ };
1818
+ }
1819
+ }
1489
1820
  if (currentText) {
1490
1821
  const streamed = streamDelta(lastLegacyText, currentText);
1491
1822
  lastLegacyText = streamed.nextText;
@@ -1500,6 +1831,10 @@ export class GatewayClient {
1500
1831
  yield { type: "done", data: payload };
1501
1832
  return;
1502
1833
  }
1834
+ if (normalizedMessage?.thinking || (normalizedMessage?.toolCalls.length ?? 0) > 0) {
1835
+ yield { type: "done", data: payload };
1836
+ return;
1837
+ }
1503
1838
  const historyText = latestHistoryAssistantText(await this.chatHistory(sessionKey, 20), acceptedRunIds);
1504
1839
  if (historyText) {
1505
1840
  yield { type: "content", text: historyText, data: payload };