@ziggs-ai/api-client 0.1.3 → 0.1.4

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.
Files changed (49) hide show
  1. package/README.md +13 -7
  2. package/dist/ConnectionManager.d.ts +45 -0
  3. package/dist/ConnectionManager.js +118 -0
  4. package/dist/http/AgentSearchClient.d.ts +36 -0
  5. package/dist/http/AgentSearchClient.js +72 -0
  6. package/dist/http/AgreementClient.d.ts +153 -0
  7. package/dist/http/AgreementClient.js +457 -0
  8. package/dist/http/ArtifactsClient.d.ts +48 -0
  9. package/dist/http/ArtifactsClient.js +90 -0
  10. package/dist/http/ChatClient.d.ts +14 -0
  11. package/dist/http/ChatClient.js +69 -0
  12. package/dist/http/MarketplaceClient.d.ts +19 -0
  13. package/dist/http/MarketplaceClient.js +72 -0
  14. package/dist/http/MessagesClient.d.ts +22 -0
  15. package/dist/http/MessagesClient.js +41 -0
  16. package/dist/http/ScopeClient.d.ts +33 -0
  17. package/dist/http/ScopeClient.js +39 -0
  18. package/dist/http/TaskClient.d.ts +75 -0
  19. package/dist/http/TaskClient.js +343 -0
  20. package/dist/http/TelemetryClient.d.ts +11 -0
  21. package/dist/http/TelemetryClient.js +53 -0
  22. package/dist/http/index.d.ts +12 -0
  23. package/dist/http/index.js +9 -0
  24. package/dist/index.d.ts +9 -0
  25. package/{src → dist}/index.js +2 -12
  26. package/dist/shared/runtimeLog.d.ts +14 -0
  27. package/dist/shared/runtimeLog.js +64 -0
  28. package/dist/types.d.ts +120 -0
  29. package/dist/types.js +50 -0
  30. package/dist/utils/urlUtils.d.ts +2 -0
  31. package/dist/utils/urlUtils.js +8 -0
  32. package/dist/websocket/ControlSocket.d.ts +13 -0
  33. package/dist/websocket/ControlSocket.js +36 -0
  34. package/dist/websocket/WebSocketClient.d.ts +71 -0
  35. package/dist/websocket/WebSocketClient.js +217 -0
  36. package/dist/websocket/index.js +1 -0
  37. package/package.json +13 -6
  38. package/src/ConnectionManager.js +0 -179
  39. package/src/http/AgentSearchClient.js +0 -113
  40. package/src/http/ContextReader.js +0 -99
  41. package/src/http/ContextWriter.js +0 -98
  42. package/src/http/TaskClient.js +0 -612
  43. package/src/http/TelemetryClient.js +0 -43
  44. package/src/http/index.js +0 -6
  45. package/src/types.js +0 -28
  46. package/src/utils/urlUtils.js +0 -17
  47. package/src/websocket/ControlSocket.js +0 -55
  48. package/src/websocket/WebSocketClient.js +0 -318
  49. /package/{src/websocket/index.js → dist/websocket/index.d.ts} +0 -0
@@ -1,43 +0,0 @@
1
- import 'dotenv/config';
2
- import { getBackendUrl } from '../utils/urlUtils.js';
3
-
4
- export class TelemetryClient {
5
- constructor(operatorKey, agentId) {
6
- if (!operatorKey) throw new Error('TelemetryClient: operatorKey is required');
7
- if (!agentId) throw new Error('TelemetryClient: agentId is required (operator-token impersonation)');
8
- this.operatorKey = operatorKey;
9
- this.agentId = agentId;
10
- this._queue = [];
11
- this._flushing = false;
12
- }
13
-
14
- async send(payload) {
15
- this._queue.push(payload);
16
- if (!this._flushing) this._flush();
17
- }
18
-
19
- async _flush() {
20
- this._flushing = true;
21
- while (this._queue.length > 0) {
22
- const batch = this._queue.splice(0, 10);
23
- await Promise.allSettled(batch.map((p) => this._post(p)));
24
- }
25
- this._flushing = false;
26
- }
27
-
28
- async _post(payload) {
29
- try {
30
- await fetch(`${getBackendUrl()}/agents/monitoring/ingest`, {
31
- method: 'POST',
32
- headers: {
33
- 'content-type': 'application/json',
34
- 'Authorization': `Bearer ${this.operatorKey}`,
35
- 'X-Agent-Id': this.agentId,
36
- },
37
- body: JSON.stringify({ payload }),
38
- });
39
- } catch (err) {
40
- console.warn(`[Telemetry] ingest failed: ${err.message}`);
41
- }
42
- }
43
- }
package/src/http/index.js DELETED
@@ -1,6 +0,0 @@
1
- // HTTP API Clients
2
- export * from './TaskClient.js';
3
- export { ContextReader } from './ContextReader.js';
4
- export { ContextWriter } from './ContextWriter.js';
5
- export { AgentSearchClient } from './AgentSearchClient.js';
6
- export { TelemetryClient } from './TelemetryClient.js';
package/src/types.js DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Type constants for the Ziggs API
3
- * These types are used when interacting with the backend timeline/history API
4
- */
5
-
6
- // Entry Types (entryType) - The kind of timeline entry
7
- export const EntryTypes = {
8
- MESSAGE: 'message',
9
- NOTIFICATION: 'notification',
10
- ARTIFACT: 'artifact',
11
- TASK_HISTORY: 'task_history',
12
- };
13
-
14
- // Content Types (content_type) - The type of content within an entry
15
- export const ContentTypes = {
16
- TEXT: 'text',
17
- OPERATION: 'operation',
18
- CONTEXT: 'context',
19
- RESULT: 'result',
20
- TASK_UPDATE: 'task_update',
21
- THOUGHT: 'thought',
22
- };
23
-
24
- /**
25
- * Open ledger proposals: proposal.proposedTo === this value means any agent can claim (approve).
26
- * ⚠️ SYNC: Keep in sync with backend/src/tasks/task-core.ts and agent-sdk/src/tasks/taskCore.js
27
- */
28
- export const OPEN_PROPOSAL_TARGET = 'everyone';
@@ -1,17 +0,0 @@
1
- /**
2
- * Gets the HTTP backend URL from HTTP_URL env var or defaults to api.ziggsai.com
3
- */
4
- export function getBackendUrl() {
5
- const url = process.env.HTTP_URL || 'https://api.ziggsai.com';
6
- // Ensure it has a protocol
7
- return url.startsWith('http') ? url : `https://${url}`;
8
- }
9
-
10
- /**
11
- * Gets the WebSocket URL from WS_URL env var or defaults to wss://api.ziggsai.com
12
- */
13
- export function getWebSocketUrl() {
14
- const wsUrl = process.env.WS_URL || 'wss://api.ziggsai.com';
15
- // Ensure it has a protocol
16
- return wsUrl.startsWith('ws') ? wsUrl : `wss://${wsUrl}`;
17
- }
@@ -1,55 +0,0 @@
1
- import { io } from 'socket.io-client';
2
-
3
- /**
4
- * createControlSocket — raw launcher-control protocol.
5
- *
6
- * Opens a single WebSocket to the backend identified as `role=launcher`, sends
7
- * `launcher:register { agentIds }` on every connect, and invokes `onWake(id)`
8
- * when the backend pushes a `launcher:wake { agentId }` frame.
9
- *
10
- * This is the minimum a non-SDK integrator needs in order to participate in
11
- * the "agents are available" story. Drop-in for other languages by mirroring
12
- * the three frames: connect with `?role=launcher`, emit `launcher:register`,
13
- * handle `launcher:wake`.
14
- *
15
- * @param {object} opts
16
- * @param {string} opts.wsUrl Backend WebSocket URL
17
- * @param {string} opts.operatorKey Operator key (JWT) with `launcher` scope.
18
- * @param {() => string[]} opts.agentIds Returns the current registered agentIds; called on every (re)connect
19
- * @param {(agentId: string) => void} opts.onWake Invoked when the backend requests a wake
20
- * @returns {{ close: () => void, resync: () => void, socket: import('socket.io-client').Socket }}
21
- */
22
- export function createControlSocket({ wsUrl, operatorKey, agentIds, onWake }) {
23
- if (!wsUrl || !operatorKey) throw new Error('[createControlSocket] wsUrl and operatorKey are required');
24
- if (typeof agentIds !== 'function') throw new Error('[createControlSocket] agentIds must be a function returning string[]');
25
- if (typeof onWake !== 'function') throw new Error('[createControlSocket] onWake must be a function');
26
-
27
- const socket = io(wsUrl, {
28
- auth: { token: `Bearer ${operatorKey}` },
29
- query: { token: operatorKey, role: 'launcher' },
30
- extraHeaders: { Authorization: `Bearer ${operatorKey}` },
31
- transports: ['websocket'],
32
- reconnection: true,
33
- reconnectionDelay: 1000,
34
- reconnectionDelayMax: 5000,
35
- });
36
-
37
- socket.on('connect', () => {
38
- const ids = agentIds();
39
- console.log(`[ControlSocket] connected (${socket.id}); registering ${ids.length} agent(s)`);
40
- socket.emit('launcher:register', { agentIds: ids });
41
- });
42
-
43
- socket.on('launcher:wake', ({ agentId }) => {
44
- if (agentId) onWake(agentId);
45
- });
46
-
47
- socket.on('disconnect', (reason) => console.log(`[ControlSocket] disconnected: ${reason}`));
48
- socket.on('connect_error', (err) => console.warn(`[ControlSocket] connect error: ${err.message}`));
49
-
50
- return {
51
- socket,
52
- close: () => socket.disconnect(),
53
- resync: () => { if (socket.connected) socket.emit('launcher:register', { agentIds: agentIds() }); },
54
- };
55
- }
@@ -1,318 +0,0 @@
1
- import { io } from "socket.io-client";
2
- import 'dotenv/config';
3
- import { getWebSocketUrl } from '../utils/urlUtils.js';
4
-
5
- class SessionState {
6
- constructor() {
7
- this.chatId = null;
8
- this.userId = null;
9
- }
10
-
11
- updateFromPayload(payload) {
12
-
13
- if (!payload || typeof payload !== 'object') {
14
- throw new Error('SessionState.updateFromPayload: payload must be an object');
15
- }
16
-
17
- if (payload.chatId) {
18
- if (typeof payload.chatId !== 'string') {
19
- throw new Error('SessionState.updateFromPayload: chatId must be a string');
20
- }
21
- this.chatId = payload.chatId;
22
- }
23
-
24
- if (payload.senderId) {
25
- if (typeof payload.senderId !== 'string') {
26
- throw new Error('SessionState.updateFromPayload: senderId must be a string');
27
- }
28
- this.userId = payload.senderId;
29
- }
30
- }
31
-
32
- isReady() {
33
- return !!(this.userId && this.chatId);
34
- }
35
-
36
- reset() {
37
- this.chatId = null;
38
- this.userId = null;
39
- }
40
-
41
- getMetadata() {
42
- return {
43
- chatId: this.chatId,
44
- userId: this.userId
45
- };
46
- }
47
- }
48
-
49
- export class WebSocketClient {
50
- constructor(options = {}) {
51
- this.wsUrl = options.wsUrl || getWebSocketUrl();
52
- this.operatorKey = options.operatorKey || process.env.ZIGGS_OPERATOR_KEY || null;
53
- this.agentId = options.agentId || null;
54
- this.label = options.label || 'agent';
55
- this.agentName = options.name || null;
56
- this.sessionState = new SessionState();
57
- this.socket = null;
58
- this.messageHandler = null;
59
- this.sessionOperationLimit = options.sessionOperationLimit ?? 30;
60
- // Per-chat operation counters — keyed by chatId to avoid cross-session interference
61
- this.operationCounts = new Map();
62
- }
63
-
64
- setMessageHandler(handler) {
65
- this.messageHandler = handler;
66
- }
67
-
68
- connect() {
69
- if (this.socket?.connected) {
70
- return;
71
- }
72
-
73
- console.log(`[${this.label}] Connecting to ${this.wsUrl}`);
74
- const socketOptions = this.buildSocketOptions();
75
- this.socket = io(this.wsUrl, socketOptions);
76
-
77
- this.socket.on('connect', () => {
78
- console.log(`[${this.label}] Connected`);
79
- });
80
-
81
- this.socket.on('connect_error', (error) => {
82
- console.error(`[${this.label}] Connection error: ${error.message}`);
83
- });
84
-
85
- this.socket.on('disconnect', (reason) => {
86
- const reasonDescriptions = {
87
- 'io server disconnect': 'Server forcefully disconnected the client',
88
- 'io client disconnect': 'Client manually disconnected',
89
- 'ping timeout': 'Server did not respond to ping (connection timeout)',
90
- 'transport close': 'Connection closed by transport layer',
91
- 'transport error': 'Transport error occurred',
92
- 'parse error': 'Error parsing server message',
93
- 'forced close': 'Connection was forcibly closed',
94
- 'forced server close': 'Server forcibly closed the connection'
95
- };
96
-
97
- const description = reasonDescriptions[reason] || 'Unknown disconnect reason';
98
- console.log(`[${this.label}] Disconnected: ${reason} — ${description}`);
99
-
100
- if (reason === 'io server disconnect') {
101
- console.log(`[${this.label}] Server disconnected — resetting session state`);
102
- this.sessionState.reset();
103
- this.operationCounts.clear();
104
- }
105
- });
106
-
107
- this.socket.on('messages', async (payload) => {
108
- try {
109
- await this.handleIncomingMessage(payload);
110
- } catch (error) {
111
- console.error(`[${this.label}] Error handling incoming message:`, error.message);
112
- throw error;
113
- }
114
- });
115
-
116
- this.socket.on('error', (error) => {
117
- console.error(`[${this.label}] Socket.IO error:`, error);
118
- });
119
- }
120
-
121
- connectAsync(timeout = 10_000) {
122
- if (this.socket?.connected) return Promise.resolve(this);
123
- return new Promise((resolve, reject) => {
124
- this.connect();
125
- const timer = setTimeout(
126
- () => reject(new Error(`[${this.label}] Connection timeout after ${timeout}ms`)),
127
- timeout
128
- );
129
- this.socket.once('connect', () => { clearTimeout(timer); resolve(this); });
130
- this.socket.once('connect_error', (err) => { clearTimeout(timer); reject(err); });
131
- });
132
- }
133
-
134
- disconnect() {
135
- if (this.socket) {
136
- this.socket.disconnect();
137
- this.socket = null;
138
- }
139
- }
140
-
141
- sendResponse(content, options = {}) {
142
-
143
- if (typeof content !== 'string' || content.trim().length === 0) {
144
- throw new Error('sendResponse: content must be a non-empty string');
145
- }
146
-
147
- const explicitReceiver = options.receiverId;
148
- const receiver = explicitReceiver || this.sessionState.userId;
149
-
150
- if (!receiver || typeof receiver !== 'string') {
151
- throw new Error('sendResponse: receiver is required (provide via options.receiverId or ensure session state has userId)');
152
- }
153
-
154
- if (!explicitReceiver) {
155
- console.warn(`[${this.label}] sendResponse: no receiverId in options, falling back to sessionState.userId (${receiver}) — pass receiverId explicitly to avoid cross-chat races`);
156
- }
157
-
158
- const explicitChatId = options.chatId;
159
- const chatId = explicitChatId || this.sessionState.chatId;
160
-
161
- if (!chatId) {
162
- throw new Error('sendResponse: chatId is required (provide via options.chatId or ensure session state has chatId)');
163
- }
164
-
165
- if (!explicitChatId) {
166
- console.warn(`[${this.label}] sendResponse: no chatId in options, falling back to sessionState.chatId (${chatId}) — pass chatId explicitly to avoid cross-chat races`);
167
- }
168
-
169
- if (!this.socket) {
170
- throw new Error('sendResponse: socket is not initialized. Call connect() first.');
171
- }
172
- if (!this.socket.connected) {
173
- throw new Error('sendResponse: socket is not connected. Call connect() and wait for connection.');
174
- }
175
-
176
- const messageId = this.generateMessageId();
177
- if (!messageId || typeof messageId !== 'string') {
178
- throw new Error('sendResponse: failed to generate valid messageId');
179
- }
180
-
181
- // Backend socket handler requires entryType + content_type (same as frontend useMessages).
182
- const message = {
183
- receiverId: receiver,
184
- chatId,
185
- messageId: messageId,
186
- text: content,
187
- entryType: options.entryType || 'message',
188
- content_type: options.content_type || options.contentType || 'text'
189
- };
190
-
191
- // Add service object with task if taskId provided
192
- if (options.taskId) {
193
- message.service = { task: { taskId: options.taskId } };
194
- }
195
-
196
- if (!message.receiverId || !message.chatId || !message.messageId || !message.text) {
197
- throw new Error('sendResponse: message validation failed - missing required fields');
198
- }
199
-
200
- const messagePreview = content.length > 100 ? content.substring(0, 100) + '...' : content;
201
- console.log(`📤 Sending message - chatId: ${message.chatId}, receiverId: ${message.receiverId}${options.receiverId ? ' (routed)' : ' (default)'}\n Message: ${messagePreview}`);
202
-
203
- this.socket.emit('chat', message);
204
- }
205
-
206
- getSessionMetadata() {
207
- return this.sessionState.getMetadata();
208
- }
209
-
210
- isConnected() {
211
- return this.socket?.connected || false;
212
- }
213
-
214
- isSessionReady() {
215
- return this.sessionState.isReady();
216
- }
217
-
218
- async handleIncomingMessage(payload) {
219
-
220
- if (!payload || typeof payload !== 'object') {
221
- throw new Error('handleIncomingMessage: payload must be an object');
222
- }
223
-
224
- if (!payload.text || typeof payload.text !== 'string' || payload.text.trim().length === 0) {
225
- throw new Error('handleIncomingMessage: payload.text is required and must be a non-empty string');
226
- }
227
-
228
- if (!payload.chatId || typeof payload.chatId !== 'string') {
229
- throw new Error('handleIncomingMessage: payload.chatId is required and must be a string');
230
- }
231
-
232
- // Extract sender info from new format: sender is { id, type } object
233
- const senderId = payload.sender?.id;
234
- const senderType = payload.sender?.type;
235
-
236
- if (!senderId || typeof senderId !== 'string') {
237
- throw new Error('handleIncomingMessage: payload.sender.id is required and must be a string');
238
- }
239
-
240
- console.log(`📥 Received message - chatId: ${payload.chatId}, sender: ${senderId} (${senderType})`);
241
-
242
- // Ignore own messages to prevent infinite loops
243
- if (this.agentId && senderId === this.agentId) {
244
- return;
245
- }
246
-
247
- // Update session state with extracted senderId (last-seen, for backward compat)
248
- this.sessionState.updateFromPayload({ chatId: payload.chatId, senderId });
249
-
250
- // Enforce per-chat operation limit to prevent infinite loops
251
- const chatOpCount = (this.operationCounts.get(payload.chatId) || 0) + 1;
252
- this.operationCounts.set(payload.chatId, chatOpCount);
253
- if (chatOpCount > this.sessionOperationLimit) {
254
- console.warn(`⚠️ Session operation limit (${this.sessionOperationLimit}) reached for chatId: ${payload.chatId}. Dropping message.`);
255
- await this.sendResponse(
256
- 'This session has reached its operation limit. Please start a new conversation.',
257
- { receiverId: senderId, chatId: payload.chatId }
258
- );
259
- return;
260
- }
261
-
262
- const text = payload.text;
263
-
264
- if (!this.messageHandler) {
265
- throw new Error('handleIncomingMessage: messageHandler is not set. Call setMessageHandler() first.');
266
- }
267
-
268
- const metadata = {
269
- chatId: payload.chatId,
270
- userId: senderId,
271
- sender: payload.sender,
272
- senderId: senderId,
273
- senderType: senderType,
274
- receiver: payload.receiver || null,
275
- receiverId: payload.receiver?.id || null,
276
- entryType: payload.entryType,
277
- content_type: payload.content_type,
278
- service: payload.service || null,
279
- taskId: payload.service?.task?.taskId || null
280
- };
281
-
282
- await this.messageHandler(text, metadata);
283
- }
284
-
285
- buildSocketOptions() {
286
- const options = {
287
- transports: ["websocket"],
288
- reconnection: true,
289
- reconnectionAttempts: Infinity,
290
- reconnectionDelay: 1000,
291
- reconnectionDelayMax: 5000,
292
- timeout: 20000,
293
- autoConnect: true,
294
- forceNew: false,
295
- multiplex: true,
296
- };
297
-
298
- if (this.operatorKey) {
299
- const bearer = `Bearer ${this.operatorKey}`;
300
- // Send token in multiple ways for maximum compatibility:
301
- // 1. Auth object (Socket.IO native)
302
- options.auth = { token: bearer };
303
- if (this.agentName) options.auth.agentName = this.agentName;
304
- // 2. Extra headers (works for direct connections)
305
- options.extraHeaders = { Authorization: bearer };
306
- // 3. Query parameter (works when proxies strip headers, e.g., Cloud Load Balancer)
307
- options.query = { token: bearer };
308
- if (this.agentId) options.query.agentId = this.agentId;
309
- if (this.agentName) options.query.agentName = this.agentName;
310
- }
311
-
312
- return options;
313
- }
314
-
315
- generateMessageId() {
316
- return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
317
- }
318
- }