@ziggs-ai/api-client 0.1.29 → 0.2.0
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 +5 -19
- package/dist/ConnectionManager.d.ts +24 -5
- package/dist/ConnectionManager.js +58 -14
- package/dist/http/ArtifactsClient.d.ts +21 -1
- package/dist/http/ArtifactsClient.js +38 -6
- package/dist/http/ConnectionsClient.d.ts +89 -0
- package/dist/http/ConnectionsClient.js +154 -0
- package/dist/http/ContextGrantsClient.d.ts +27 -0
- package/dist/http/ContextGrantsClient.js +25 -0
- package/dist/http/InboxClient.d.ts +35 -1
- package/dist/http/InboxClient.js +25 -2
- package/dist/http/PaymentsClient.d.ts +94 -0
- package/dist/http/PaymentsClient.js +231 -0
- package/dist/http/TaskClient.d.ts +8 -0
- package/dist/http/grantRails.d.ts +20 -0
- package/dist/http/grantRails.js +50 -0
- package/dist/http/index.d.ts +7 -1
- package/dist/http/index.js +3 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/package.json +1 -1
- package/dist/websocket/ControlSocket.d.ts +0 -13
- package/dist/websocket/ControlSocket.js +0 -37
- package/dist/websocket/WebSocketClient.d.ts +0 -86
- package/dist/websocket/WebSocketClient.js +0 -268
- package/dist/websocket/index.d.ts +0 -1
- package/dist/websocket/index.js +0 -1
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { io } from 'socket.io-client';
|
|
2
|
-
import 'dotenv/config';
|
|
3
|
-
import { getWebSocketUrl } from '../utils/urlUtils.js';
|
|
4
|
-
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
5
|
-
export class WebSocketClient {
|
|
6
|
-
wsUrl;
|
|
7
|
-
operatorKey;
|
|
8
|
-
agentId;
|
|
9
|
-
userToken;
|
|
10
|
-
label;
|
|
11
|
-
socket;
|
|
12
|
-
messageHandler;
|
|
13
|
-
resourceEventHandler;
|
|
14
|
-
constructor(options = {}) {
|
|
15
|
-
this.wsUrl = options.wsUrl || getWebSocketUrl();
|
|
16
|
-
this.userToken = options.userToken || null;
|
|
17
|
-
this.operatorKey = this.userToken ? null : (options.operatorKey || process.env.ZIGGS_OPERATOR_KEY || null);
|
|
18
|
-
this.agentId = this.userToken ? null : (options.agentId || null);
|
|
19
|
-
this.label = options.label || 'agent';
|
|
20
|
-
this.socket = null;
|
|
21
|
-
this.messageHandler = null;
|
|
22
|
-
this.resourceEventHandler = null;
|
|
23
|
-
}
|
|
24
|
-
setMessageHandler(handler) {
|
|
25
|
-
this.messageHandler = handler;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Subscribe to `resource_changed` notifications — the unified push channel
|
|
29
|
-
* for non-message resource changes (artifact, task-state, agreement). The
|
|
30
|
-
* agent decides whether to pull the corresponding primitive based on
|
|
31
|
-
* `event.kind` + ids. Replaces cursor-based polling for delta detection.
|
|
32
|
-
*/
|
|
33
|
-
setResourceEventHandler(handler) {
|
|
34
|
-
this.resourceEventHandler = handler;
|
|
35
|
-
}
|
|
36
|
-
connectHandlers = [];
|
|
37
|
-
/**
|
|
38
|
-
* Called on every socket connect, including socket.io reconnects.
|
|
39
|
-
* Used for inbox catch-up (ZIG-454) so missed pushes are recovered from MongoDB.
|
|
40
|
-
*/
|
|
41
|
-
onConnect(handler) {
|
|
42
|
-
this.connectHandlers.push(handler);
|
|
43
|
-
}
|
|
44
|
-
async _emitConnect() {
|
|
45
|
-
for (const handler of this.connectHandlers) {
|
|
46
|
-
try {
|
|
47
|
-
await handler();
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
51
|
-
runtimeLog.warn(this.label, `connect handler error: ${msg}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
_connect() {
|
|
56
|
-
if (this.socket?.connected)
|
|
57
|
-
return;
|
|
58
|
-
runtimeLog.debug(this.label, `Connecting to ${this.wsUrl}`);
|
|
59
|
-
const socketOptions = this.buildSocketOptions();
|
|
60
|
-
this.socket = io(this.wsUrl, socketOptions);
|
|
61
|
-
this.socket.on('connect', () => {
|
|
62
|
-
runtimeLog.info(this.label, 'Connected');
|
|
63
|
-
void this._emitConnect();
|
|
64
|
-
});
|
|
65
|
-
this.socket.on('connect_error', (error) => {
|
|
66
|
-
runtimeLog.error(this.label, `Connection error: ${error.message}`);
|
|
67
|
-
});
|
|
68
|
-
this.socket.on('disconnect', (reason) => {
|
|
69
|
-
const reasonDescriptions = {
|
|
70
|
-
'io server disconnect': 'Server forcefully disconnected the client',
|
|
71
|
-
'io client disconnect': 'Client manually disconnected',
|
|
72
|
-
'ping timeout': 'Server did not respond to ping (connection timeout)',
|
|
73
|
-
'transport close': 'Connection closed by transport layer',
|
|
74
|
-
'transport error': 'Transport error occurred',
|
|
75
|
-
'parse error': 'Error parsing server message',
|
|
76
|
-
'forced close': 'Connection was forcibly closed',
|
|
77
|
-
'forced server close': 'Server forcibly closed the connection',
|
|
78
|
-
};
|
|
79
|
-
const description = reasonDescriptions[reason] || 'Unknown disconnect reason';
|
|
80
|
-
runtimeLog.debug(this.label, `Disconnected: ${reason} — ${description}`);
|
|
81
|
-
});
|
|
82
|
-
// ZIG-207: subscribe to the canonical event name. Backend dual-emits
|
|
83
|
-
// `messages` + `chat:message:new` with byte-identical payloads, so
|
|
84
|
-
// listening to ONE of the two is exactly correct — listening to both
|
|
85
|
-
// would double-invoke handleIncomingMessage(). We pick the canonical
|
|
86
|
-
// namespaced name so once the sunset PR drops the legacy `messages`
|
|
87
|
-
// emit, no client change is needed.
|
|
88
|
-
this.socket.on('chat:message:new', async (payload) => {
|
|
89
|
-
try {
|
|
90
|
-
await this.handleIncomingMessage(payload);
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
94
|
-
runtimeLog.error(this.label, `Error handling incoming message: ${msg}`);
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
this.socket.on('resource_changed', async (payload) => {
|
|
99
|
-
if (!this.resourceEventHandler)
|
|
100
|
-
return;
|
|
101
|
-
try {
|
|
102
|
-
await this.resourceEventHandler(payload);
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
106
|
-
runtimeLog.error(this.label, `Error handling resource_changed: ${msg}`);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
this.socket.on('error', (error) => {
|
|
110
|
-
runtimeLog.error(this.label, `Socket.IO error: ${String(error)}`);
|
|
111
|
-
});
|
|
112
|
-
// ZIG-860: the send path is fire-and-forget (no ack callback), so the
|
|
113
|
-
// backend's structured rejection is the ONLY failure signal. Not listening
|
|
114
|
-
// made every rejected send silent — an agent's message just vanished (the
|
|
115
|
-
// a2a schema bug hid behind exactly this). Log loudly; delivery recovery
|
|
116
|
-
// stays with the inbox/catch-up machinery.
|
|
117
|
-
this.socket.on('chat:error', (payload) => {
|
|
118
|
-
const p = (payload ?? {});
|
|
119
|
-
runtimeLog.error(this.label, `chat:error [${p['code'] ?? 'UNKNOWN'}] chatId=${p['chatId'] ?? '-'} messageId=${p['messageId'] ?? '-'}: ${p['message'] ?? ''}`);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
connectAsync(timeout = 10_000) {
|
|
123
|
-
if (this.socket?.connected)
|
|
124
|
-
return Promise.resolve(this);
|
|
125
|
-
return new Promise((resolve, reject) => {
|
|
126
|
-
this._connect();
|
|
127
|
-
// Only reject on timeout — not on the first connect_error. socket.io
|
|
128
|
-
// retries automatically (reconnectionAttempts: Infinity), so a single
|
|
129
|
-
// network hiccup or a briefly-down backend shouldn't crash the caller.
|
|
130
|
-
const timer = setTimeout(() => reject(new Error(`[${this.label}] Connection timeout after ${timeout}ms`)), timeout);
|
|
131
|
-
this.socket.once('connect', () => { clearTimeout(timer); resolve(this); });
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
disconnect() {
|
|
135
|
-
if (this.socket) {
|
|
136
|
-
this.socket.disconnect();
|
|
137
|
-
this.socket = null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
send(chatId, receiverId, content, options = {}) {
|
|
141
|
-
if (!chatId || typeof chatId !== 'string')
|
|
142
|
-
throw new Error('send: chatId is required');
|
|
143
|
-
if (!receiverId || typeof receiverId !== 'string')
|
|
144
|
-
throw new Error('send: receiverId is required');
|
|
145
|
-
if (typeof content !== 'string' || content.trim().length === 0)
|
|
146
|
-
throw new Error('send: content must be a non-empty string');
|
|
147
|
-
if (!this.socket)
|
|
148
|
-
throw new Error('send: socket is not initialized. Call connect() first.');
|
|
149
|
-
if (!this.socket.connected)
|
|
150
|
-
throw new Error('send: socket is not connected. Call connect() and wait for connection.');
|
|
151
|
-
const messageId = options.messageId ?? this.generateMessageId();
|
|
152
|
-
if (!messageId || typeof messageId !== 'string')
|
|
153
|
-
throw new Error('send: failed to generate valid messageId');
|
|
154
|
-
const message = {
|
|
155
|
-
receiverId,
|
|
156
|
-
chatId,
|
|
157
|
-
messageId,
|
|
158
|
-
text: content,
|
|
159
|
-
entryType: options.entryType || 'message',
|
|
160
|
-
content_type: options.content_type || options.contentType || 'text',
|
|
161
|
-
};
|
|
162
|
-
const messagePreview = content.length > 100 ? content.substring(0, 100) + '...' : content;
|
|
163
|
-
// ZIG-207: emit the canonical namespaced inbound event. Backend
|
|
164
|
-
// registers `chat:message:send` as a one-line alias on `handleChat`,
|
|
165
|
-
// so this is byte-identical to the legacy `chat` emit. Streaming
|
|
166
|
-
// chunks keep their existing `chat:chunk` name (already namespaced).
|
|
167
|
-
const event = options.partial ? 'chat:chunk' : 'chat:message:send';
|
|
168
|
-
runtimeLog.debug(this.label, `Sending message (${event}) - chatId: ${chatId}, receiverId: ${receiverId}\n Message: ${messagePreview}`);
|
|
169
|
-
this.socket.emit(event, message);
|
|
170
|
-
}
|
|
171
|
-
isConnected() {
|
|
172
|
-
return this.socket?.connected || false;
|
|
173
|
-
}
|
|
174
|
-
async handleIncomingMessage(payload) {
|
|
175
|
-
if (!payload || typeof payload !== 'object') {
|
|
176
|
-
throw new Error('handleIncomingMessage: payload must be an object');
|
|
177
|
-
}
|
|
178
|
-
const p = payload;
|
|
179
|
-
if (!p['text'] || typeof p['text'] !== 'string' || p['text'].trim().length === 0) {
|
|
180
|
-
throw new Error('handleIncomingMessage: payload.text is required and must be a non-empty string');
|
|
181
|
-
}
|
|
182
|
-
if (!p['chatId'] || typeof p['chatId'] !== 'string') {
|
|
183
|
-
throw new Error('handleIncomingMessage: payload.chatId is required and must be a string');
|
|
184
|
-
}
|
|
185
|
-
const sender = p['sender'];
|
|
186
|
-
const senderId = sender?.['id'];
|
|
187
|
-
const senderType = sender?.['type'];
|
|
188
|
-
if (!senderId || typeof senderId !== 'string') {
|
|
189
|
-
throw new Error('handleIncomingMessage: payload.sender.id is required and must be a string');
|
|
190
|
-
}
|
|
191
|
-
runtimeLog.debug(this.label, `Received message - chatId: ${p['chatId']}, sender: ${senderId} (${senderType})`);
|
|
192
|
-
if (this.agentId && senderId === this.agentId)
|
|
193
|
-
return;
|
|
194
|
-
if (!this.messageHandler) {
|
|
195
|
-
throw new Error('handleIncomingMessage: messageHandler is not set. Call setMessageHandler() first.');
|
|
196
|
-
}
|
|
197
|
-
const chatId = p['chatId'];
|
|
198
|
-
const receiver = p['receiver'];
|
|
199
|
-
const task = p['task'] ?? null;
|
|
200
|
-
const agreement = p['agreement'] ?? null;
|
|
201
|
-
const operation = p['operation'] ?? null;
|
|
202
|
-
const agreementId = p['agreementId'] ?? null;
|
|
203
|
-
runtimeLog.info(this.label, `[wire-debug] incoming chatId=${chatId} sender=${senderId} content_type=${p['content_type'] ?? p['contentType'] ?? '<none>'} entryType=${p['entryType'] ?? '<none>'} ts=${typeof p['timestamp']}/${typeof p['sentTimestamp']} keys=${Object.keys(p).join(',')} task?=${task ? `taskId=${task['taskId']} state=${task['state']}` : 'no'} agreement?=${agreement ? `agreementId=${agreement['agreementId']} status=${agreement['status']}` : 'no'} operation?=${operation ?? 'no'}`);
|
|
204
|
-
const metadata = {
|
|
205
|
-
chatId,
|
|
206
|
-
messageId: typeof p['messageId'] === 'string' ? p['messageId'] : undefined,
|
|
207
|
-
userId: senderId,
|
|
208
|
-
sender: { id: senderId, type: senderType },
|
|
209
|
-
senderId,
|
|
210
|
-
senderType,
|
|
211
|
-
receiver: receiver || null,
|
|
212
|
-
receiverId: receiver?.['id'] || null,
|
|
213
|
-
entryType: p['entryType'],
|
|
214
|
-
content_type: (p['content_type'] ?? p['contentType']),
|
|
215
|
-
taskId: task?.['taskId'] ?? null,
|
|
216
|
-
task,
|
|
217
|
-
agreement,
|
|
218
|
-
operation,
|
|
219
|
-
agreementId,
|
|
220
|
-
// ZIG-860: carry the send time through — the live-path watermark ack
|
|
221
|
-
// (AgentHost, ZIG-832 P4) reads it, and dropping it here meant the ack
|
|
222
|
-
// never fired for any live message.
|
|
223
|
-
timestamp: typeof p['timestamp'] === 'string' ? p['timestamp'] : undefined,
|
|
224
|
-
sentTimestamp: typeof p['sentTimestamp'] === 'string' ? p['sentTimestamp'] : undefined,
|
|
225
|
-
};
|
|
226
|
-
await this.messageHandler(p['text'], metadata);
|
|
227
|
-
}
|
|
228
|
-
buildSocketOptions() {
|
|
229
|
-
// `forceNew: true` is required when multiple WebSocketClient instances
|
|
230
|
-
// hit the same URL with DIFFERENT auth. socket.io's default
|
|
231
|
-
// `multiplex: true` shares the engine.io Manager across `io()` calls
|
|
232
|
-
// to the same URL — and the Manager's auth is fixed by whoever opened
|
|
233
|
-
// it first. So a second client (e.g. an eval's user-mode connection
|
|
234
|
-
// alongside agent-host operator-key connections) would silently inherit
|
|
235
|
-
// the first client's auth and get rejected. Forcing a fresh Manager
|
|
236
|
-
// per client avoids that aliasing.
|
|
237
|
-
const options = {
|
|
238
|
-
transports: ['websocket'],
|
|
239
|
-
reconnection: true,
|
|
240
|
-
reconnectionAttempts: Infinity,
|
|
241
|
-
reconnectionDelay: 1000,
|
|
242
|
-
reconnectionDelayMax: 30000,
|
|
243
|
-
randomizationFactor: 0.5,
|
|
244
|
-
timeout: 20000,
|
|
245
|
-
autoConnect: true,
|
|
246
|
-
forceNew: true,
|
|
247
|
-
multiplex: false,
|
|
248
|
-
};
|
|
249
|
-
if (this.userToken) {
|
|
250
|
-
const bearer = `Bearer ${this.userToken}`;
|
|
251
|
-
options.auth = { token: bearer };
|
|
252
|
-
options.extraHeaders = { Authorization: bearer };
|
|
253
|
-
// No agentId — the gateway sees the `userId` claim and routes as a human user.
|
|
254
|
-
}
|
|
255
|
-
else if (this.operatorKey) {
|
|
256
|
-
const bearer = `Bearer ${this.operatorKey}`;
|
|
257
|
-
options.auth = { token: bearer };
|
|
258
|
-
options.extraHeaders = { Authorization: bearer };
|
|
259
|
-
// agentId goes in query for fleet keys; agent-scoped keys omit it (ZIG-279).
|
|
260
|
-
if (this.agentId)
|
|
261
|
-
options.query = { agentId: this.agentId };
|
|
262
|
-
}
|
|
263
|
-
return options;
|
|
264
|
-
}
|
|
265
|
-
generateMessageId() {
|
|
266
|
-
return crypto.randomUUID();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { WebSocketClient } from './WebSocketClient.js';
|
package/dist/websocket/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { WebSocketClient } from './WebSocketClient.js';
|