@ziggs-ai/api-client 0.1.3 → 0.1.5

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 (72) hide show
  1. package/README.md +13 -7
  2. package/dist/ConnectionManager.d.ts +46 -0
  3. package/dist/ConnectionManager.js +132 -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 +477 -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 +34 -0
  11. package/dist/http/ChatClient.js +104 -0
  12. package/dist/http/ContextDiscoveryClient.d.ts +23 -0
  13. package/dist/http/ContextDiscoveryClient.js +35 -0
  14. package/dist/http/ContextReadClient.d.ts +33 -0
  15. package/dist/http/ContextReadClient.js +54 -0
  16. package/dist/http/MarketplaceClient.d.ts +19 -0
  17. package/dist/http/MarketplaceClient.js +72 -0
  18. package/dist/http/MessagesClient.d.ts +26 -0
  19. package/dist/http/MessagesClient.js +49 -0
  20. package/dist/http/ScopeClient.d.ts +33 -0
  21. package/dist/http/ScopeClient.js +39 -0
  22. package/dist/http/TaskClient.d.ts +75 -0
  23. package/dist/http/TaskClient.js +352 -0
  24. package/dist/http/TelemetryClient.d.ts +11 -0
  25. package/dist/http/TelemetryClient.js +53 -0
  26. package/dist/http/index.d.ts +16 -0
  27. package/dist/http/index.js +11 -0
  28. package/dist/index.d.ts +9 -0
  29. package/{src → dist}/index.js +2 -12
  30. package/dist/shared/runtimeLog.d.ts +14 -0
  31. package/dist/shared/runtimeLog.js +64 -0
  32. package/dist/types.d.ts +130 -0
  33. package/dist/types.js +50 -0
  34. package/dist/utils/urlUtils.d.ts +2 -0
  35. package/dist/utils/urlUtils.js +8 -0
  36. package/dist/websocket/ControlSocket.d.ts +13 -0
  37. package/dist/websocket/ControlSocket.js +37 -0
  38. package/dist/websocket/WebSocketClient.d.ts +71 -0
  39. package/dist/websocket/WebSocketClient.js +233 -0
  40. package/dist/websocket/index.js +1 -0
  41. package/package.json +20 -7
  42. package/src/ConnectionManager.ts +172 -0
  43. package/src/http/AgentSearchClient.ts +115 -0
  44. package/src/http/AgreementClient.ts +721 -0
  45. package/src/http/ArtifactsClient.ts +133 -0
  46. package/src/http/ChatClient.ts +147 -0
  47. package/src/http/ContextDiscoveryClient.ts +52 -0
  48. package/src/http/ContextReadClient.ts +83 -0
  49. package/src/http/MarketplaceClient.ts +94 -0
  50. package/src/http/MessagesClient.ts +71 -0
  51. package/src/http/ScopeClient.ts +64 -0
  52. package/src/http/TaskClient.ts +450 -0
  53. package/src/http/{TelemetryClient.js → TelemetryClient.ts} +21 -7
  54. package/src/http/index.ts +26 -0
  55. package/src/index.ts +27 -0
  56. package/src/shared/runtimeLog.ts +68 -0
  57. package/src/types.ts +158 -0
  58. package/src/utils/urlUtils.ts +9 -0
  59. package/src/websocket/ControlSocket.ts +51 -0
  60. package/src/websocket/WebSocketClient.ts +315 -0
  61. package/src/websocket/index.ts +1 -0
  62. package/src/ConnectionManager.js +0 -179
  63. package/src/http/AgentSearchClient.js +0 -113
  64. package/src/http/ContextReader.js +0 -99
  65. package/src/http/ContextWriter.js +0 -98
  66. package/src/http/TaskClient.js +0 -612
  67. package/src/http/index.js +0 -6
  68. package/src/types.js +0 -28
  69. package/src/utils/urlUtils.js +0 -17
  70. package/src/websocket/ControlSocket.js +0 -55
  71. package/src/websocket/WebSocketClient.js +0 -318
  72. /package/{src/websocket/index.js → dist/websocket/index.d.ts} +0 -0
@@ -0,0 +1,130 @@
1
+ export declare class ApiError extends Error {
2
+ readonly status: number;
3
+ readonly body?: string | undefined;
4
+ constructor(message: string, status: number, body?: string | undefined);
5
+ }
6
+ export interface Creds {
7
+ operatorKey: string;
8
+ agentId: string;
9
+ }
10
+ export type TaskState = 'active' | 'proposal' | 'completed' | 'failed' | 'cancelled' | 'ledger_open';
11
+ export type PlanStepStatus = 'pending' | 'in_progress' | 'completed' | 'skipped';
12
+ export interface PlanStep {
13
+ stepId: string;
14
+ status: PlanStepStatus;
15
+ result?: unknown;
16
+ }
17
+ export interface Task {
18
+ taskId: string;
19
+ description: string;
20
+ agentId?: string;
21
+ executorId?: string;
22
+ payerId?: string;
23
+ agreementId?: string;
24
+ agreement?: Agreement;
25
+ state: TaskState;
26
+ processing?: boolean;
27
+ deleted?: boolean;
28
+ result?: unknown;
29
+ errorMessage?: string;
30
+ createdAt?: string;
31
+ updatedAt?: string;
32
+ parentTaskId?: string;
33
+ rootTaskId?: string;
34
+ plan?: PlanStep[];
35
+ history?: unknown[];
36
+ }
37
+ export type AgreementStatus = 'pending' | 'active' | 'fulfilled' | 'cancelled' | 'rejected' | (string & {});
38
+ export type ProposalStatus = 'pending' | 'approved' | 'rejected' | 'countered' | 'expired' | (string & {});
39
+ /** ⚠️ SYNC: backend src/agreements/agreements.constants.ts AGREEMENT_ENGAGEMENT_KIND */
40
+ export declare const AGREEMENT_ENGAGEMENT_KIND: {
41
+ readonly HIRE: "hire";
42
+ readonly SERVICE: "service";
43
+ };
44
+ export type EngagementKind = (typeof AGREEMENT_ENGAGEMENT_KIND)[keyof typeof AGREEMENT_ENGAGEMENT_KIND];
45
+ export interface Agreement {
46
+ agreementId: string;
47
+ description?: string;
48
+ status?: AgreementStatus;
49
+ engagementKind?: EngagementKind;
50
+ proposalStatus?: ProposalStatus;
51
+ parties?: {
52
+ proposedTo?: string;
53
+ provider?: string;
54
+ payer?: string;
55
+ creator?: string;
56
+ creatorAgent?: string;
57
+ };
58
+ /** Legacy root-level price — always null in practice. Use money.price instead. */
59
+ price?: number | null;
60
+ /** Price and payment state. price is in cents (e.g. 6000 = 6000 KRW). */
61
+ money?: {
62
+ price?: number | null;
63
+ paymentStatus?: string;
64
+ };
65
+ lifecycle?: string;
66
+ expiresAt?: string;
67
+ maxExecutions?: number;
68
+ metadata?: Record<string, unknown>;
69
+ createdAt?: string;
70
+ updatedAt?: string;
71
+ }
72
+ /** ⚠️ SYNC: Keep in sync with backend src/chat/chat.types.ts ENTRY_TYPE */
73
+ export declare const EntryTypes: {
74
+ readonly MESSAGE: "message";
75
+ readonly NOTIFICATION: "notification";
76
+ readonly ARTIFACT: "artifact";
77
+ readonly AGREEMENT_HISTORY: "agreement_history";
78
+ /** @deprecated legacy alias for AGREEMENT_HISTORY */
79
+ readonly TASK_HISTORY: "task_history";
80
+ };
81
+ export type EntryType = (typeof EntryTypes)[keyof typeof EntryTypes];
82
+ /** ⚠️ SYNC: Keep in sync with backend src/chat/chat.types.ts CONTENT_TYPE */
83
+ export declare const ContentTypes: {
84
+ readonly TEXT: "text";
85
+ readonly OPERATION: "operation";
86
+ readonly CONTEXT: "context";
87
+ readonly RESULT: "result";
88
+ readonly AGREEMENT_UPDATE: "agreement_update";
89
+ readonly TASK: "task";
90
+ readonly PROPOSAL: "proposal";
91
+ readonly THOUGHT: "thought";
92
+ /** @deprecated legacy alias for AGREEMENT_UPDATE */
93
+ readonly TASK_UPDATE: "task_update";
94
+ };
95
+ export type ContentType = (typeof ContentTypes)[keyof typeof ContentTypes];
96
+ /** ⚠️ SYNC: Mirrors backend src/chat/chat.types.ts isValidContentType */
97
+ export declare function isValidContentType(contentType: string, entryType: string): boolean;
98
+ /** ⚠️ SYNC: Keep in sync with backend. This is the canonical SDK-side definition. */
99
+ export declare const OPEN_AGREEMENT_TARGET: "everyone";
100
+ export interface MessageMetadata {
101
+ chatId: string;
102
+ userId: string;
103
+ sender: {
104
+ id: string;
105
+ type?: string;
106
+ };
107
+ senderId: string;
108
+ senderType?: string;
109
+ receiver?: {
110
+ id: string;
111
+ } | null;
112
+ receiverId?: string | null;
113
+ entryType?: string;
114
+ content_type?: string;
115
+ taskId?: string | null;
116
+ /**
117
+ * Optional task / agreement snapshot pinned by the backend on
118
+ * `task.notify.chat` / `agreement.notify.direct` traffic. The agent SDK's
119
+ * `normalizeIncomingEvent` reads `metadata.task` to upgrade structured
120
+ * task notifications into `task_result` events (so executors see
121
+ * `task-assigned` outcomes when the orchestrator calls `task_spawn`),
122
+ * and maps explicit wire `operation` + `agreementId` lifecycle fields
123
+ * into `agreement_lifecycle` events for proposal approve/reject.
124
+ */
125
+ task?: Record<string, unknown> | null;
126
+ agreement?: Record<string, unknown> | null;
127
+ operation?: string | null;
128
+ agreementId?: string | null;
129
+ }
130
+ export type MessageHandler = (text: string, metadata: MessageMetadata) => Promise<void>;
package/dist/types.js ADDED
@@ -0,0 +1,50 @@
1
+ export class ApiError extends Error {
2
+ status;
3
+ body;
4
+ constructor(message, status, body) {
5
+ super(message);
6
+ this.status = status;
7
+ this.body = body;
8
+ this.name = 'ApiError';
9
+ }
10
+ }
11
+ /** ⚠️ SYNC: backend src/agreements/agreements.constants.ts AGREEMENT_ENGAGEMENT_KIND */
12
+ export const AGREEMENT_ENGAGEMENT_KIND = {
13
+ HIRE: 'hire',
14
+ SERVICE: 'service',
15
+ };
16
+ /** ⚠️ SYNC: Keep in sync with backend src/chat/chat.types.ts ENTRY_TYPE */
17
+ export const EntryTypes = {
18
+ MESSAGE: 'message',
19
+ NOTIFICATION: 'notification',
20
+ ARTIFACT: 'artifact',
21
+ AGREEMENT_HISTORY: 'agreement_history',
22
+ /** @deprecated legacy alias for AGREEMENT_HISTORY */
23
+ TASK_HISTORY: 'task_history',
24
+ };
25
+ /** ⚠️ SYNC: Keep in sync with backend src/chat/chat.types.ts CONTENT_TYPE */
26
+ export const ContentTypes = {
27
+ TEXT: 'text',
28
+ OPERATION: 'operation',
29
+ CONTEXT: 'context',
30
+ RESULT: 'result',
31
+ AGREEMENT_UPDATE: 'agreement_update',
32
+ TASK: 'task',
33
+ PROPOSAL: 'proposal',
34
+ THOUGHT: 'thought',
35
+ /** @deprecated legacy alias for AGREEMENT_UPDATE */
36
+ TASK_UPDATE: 'task_update',
37
+ };
38
+ const VALID_CONTENT_TYPES = {
39
+ [EntryTypes.MESSAGE]: [ContentTypes.TEXT],
40
+ [EntryTypes.NOTIFICATION]: [ContentTypes.TEXT, ContentTypes.TASK, ContentTypes.PROPOSAL],
41
+ [EntryTypes.ARTIFACT]: [ContentTypes.OPERATION, ContentTypes.CONTEXT, ContentTypes.RESULT, ContentTypes.TEXT, ContentTypes.THOUGHT],
42
+ [EntryTypes.AGREEMENT_HISTORY]: [ContentTypes.AGREEMENT_UPDATE, ContentTypes.TASK_UPDATE],
43
+ [EntryTypes.TASK_HISTORY]: [ContentTypes.AGREEMENT_UPDATE, ContentTypes.TASK_UPDATE],
44
+ };
45
+ /** ⚠️ SYNC: Mirrors backend src/chat/chat.types.ts isValidContentType */
46
+ export function isValidContentType(contentType, entryType) {
47
+ return VALID_CONTENT_TYPES[entryType]?.includes(contentType) ?? false;
48
+ }
49
+ /** ⚠️ SYNC: Keep in sync with backend. This is the canonical SDK-side definition. */
50
+ export const OPEN_AGREEMENT_TARGET = 'everyone';
@@ -0,0 +1,2 @@
1
+ export declare function getBackendUrl(): string;
2
+ export declare function getWebSocketUrl(): string;
@@ -0,0 +1,8 @@
1
+ export function getBackendUrl() {
2
+ const url = process.env.HTTP_URL || 'https://api.ziggsai.com';
3
+ return url.startsWith('http') ? url : `https://${url}`;
4
+ }
5
+ export function getWebSocketUrl() {
6
+ const wsUrl = process.env.WS_URL || 'wss://api.ziggsai.com';
7
+ return wsUrl.startsWith('ws') ? wsUrl : `wss://${wsUrl}`;
8
+ }
@@ -0,0 +1,13 @@
1
+ import { type Socket } from 'socket.io-client';
2
+ export interface ControlSocketOptions {
3
+ wsUrl: string;
4
+ operatorKey: string;
5
+ agentIds: () => string[];
6
+ onWake: (agentId: string) => void;
7
+ }
8
+ export interface ControlSocketHandle {
9
+ socket: Socket;
10
+ close: () => void;
11
+ resync: () => void;
12
+ }
13
+ export declare function createControlSocket({ wsUrl, operatorKey, agentIds, onWake }: ControlSocketOptions): ControlSocketHandle;
@@ -0,0 +1,37 @@
1
+ import { io } from 'socket.io-client';
2
+ import { runtimeLog } from '../shared/runtimeLog.js';
3
+ export function createControlSocket({ wsUrl, operatorKey, agentIds, onWake }) {
4
+ if (!wsUrl || !operatorKey)
5
+ throw new Error('[createControlSocket] wsUrl and operatorKey are required');
6
+ if (typeof agentIds !== 'function')
7
+ throw new Error('[createControlSocket] agentIds must be a function returning string[]');
8
+ if (typeof onWake !== 'function')
9
+ throw new Error('[createControlSocket] onWake must be a function');
10
+ const socket = io(wsUrl, {
11
+ auth: { token: `Bearer ${operatorKey}` },
12
+ query: { token: operatorKey, role: 'launcher' },
13
+ extraHeaders: { Authorization: `Bearer ${operatorKey}` },
14
+ transports: ['websocket'],
15
+ reconnection: true,
16
+ reconnectionDelay: 1000,
17
+ reconnectionDelayMax: 30000,
18
+ randomizationFactor: 0.5,
19
+ });
20
+ socket.on('connect', () => {
21
+ const ids = agentIds();
22
+ runtimeLog.info('ControlSocket', `connected (${socket.id}); registering ${ids.length} agent(s)`);
23
+ socket.emit('launcher:register', { agentIds: ids });
24
+ });
25
+ socket.on('launcher:wake', ({ agentId }) => {
26
+ if (agentId)
27
+ onWake(agentId);
28
+ });
29
+ socket.on('disconnect', (reason) => runtimeLog.debug('ControlSocket', `disconnected: ${reason}`));
30
+ socket.on('connect_error', (err) => runtimeLog.warn('ControlSocket', `connect error: ${err.message}`));
31
+ return {
32
+ socket,
33
+ close: () => socket.disconnect(),
34
+ resync: () => { if (socket.connected)
35
+ socket.emit('launcher:register', { agentIds: agentIds() }); },
36
+ };
37
+ }
@@ -0,0 +1,71 @@
1
+ import 'dotenv/config';
2
+ import { type MessageHandler } from '../types.js';
3
+ export interface WebSocketClientOptions {
4
+ wsUrl?: string;
5
+ operatorKey?: string;
6
+ agentId?: string;
7
+ /**
8
+ * Optional user JWT (from `POST /users/login`). When set, the socket
9
+ * authenticates as that user — the gateway populates `socket.userId` and
10
+ * routes messages to them as a human user. Mutually exclusive with
11
+ * `operatorKey` + `agentId`. Used by the eval harness to seed chats as a
12
+ * real user instead of impersonating an agent.
13
+ */
14
+ userToken?: string;
15
+ label?: string;
16
+ }
17
+ /**
18
+ * Mirrors the backend `ResourceEvent` shape. Emitted on the wire as
19
+ * `resource_changed` when a resource the agent has access to mutates.
20
+ */
21
+ export interface ResourceEvent {
22
+ kind: 'message' | 'artifact' | 'task-state' | 'agreement';
23
+ ts: string;
24
+ resourceId: string;
25
+ chatId?: string;
26
+ agreementId?: string;
27
+ taskId?: string;
28
+ change?: 'created' | 'updated' | 'state-changed';
29
+ reason?: string;
30
+ }
31
+ export type ResourceEventHandler = (event: ResourceEvent) => void | Promise<void>;
32
+ export interface SendOptions {
33
+ entryType?: string;
34
+ content_type?: string;
35
+ contentType?: string;
36
+ taskId?: string;
37
+ /**
38
+ * When true, emits a 'chat:chunk' event instead of 'chat'.
39
+ * Use for streaming partial responses; send a final message
40
+ * (partial: false or omitted) to signal end of stream.
41
+ * Requires backend streaming support.
42
+ */
43
+ partial?: boolean;
44
+ }
45
+ export declare class WebSocketClient {
46
+ private wsUrl;
47
+ private operatorKey;
48
+ private agentId;
49
+ private userToken;
50
+ private label;
51
+ private socket;
52
+ private messageHandler;
53
+ private resourceEventHandler;
54
+ constructor(options?: WebSocketClientOptions);
55
+ setMessageHandler(handler: MessageHandler): void;
56
+ /**
57
+ * Subscribe to `resource_changed` notifications — the unified push channel
58
+ * for non-message resource changes (artifact, task-state, agreement). The
59
+ * agent decides whether to pull the corresponding primitive based on
60
+ * `event.kind` + ids. Replaces cursor-based polling for delta detection.
61
+ */
62
+ setResourceEventHandler(handler: ResourceEventHandler): void;
63
+ private _connect;
64
+ connectAsync(timeout?: number): Promise<this>;
65
+ disconnect(): void;
66
+ send(chatId: string, receiverId: string, content: string, options?: SendOptions): void;
67
+ isConnected(): boolean;
68
+ handleIncomingMessage(payload: unknown): Promise<void>;
69
+ private buildSocketOptions;
70
+ private generateMessageId;
71
+ }
@@ -0,0 +1,233 @@
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
+ _connect() {
37
+ if (this.socket?.connected)
38
+ return;
39
+ runtimeLog.debug(this.label, `Connecting to ${this.wsUrl}`);
40
+ const socketOptions = this.buildSocketOptions();
41
+ this.socket = io(this.wsUrl, socketOptions);
42
+ this.socket.on('connect', () => {
43
+ runtimeLog.info(this.label, 'Connected');
44
+ });
45
+ this.socket.on('connect_error', (error) => {
46
+ runtimeLog.error(this.label, `Connection error: ${error.message}`);
47
+ });
48
+ this.socket.on('disconnect', (reason) => {
49
+ const reasonDescriptions = {
50
+ 'io server disconnect': 'Server forcefully disconnected the client',
51
+ 'io client disconnect': 'Client manually disconnected',
52
+ 'ping timeout': 'Server did not respond to ping (connection timeout)',
53
+ 'transport close': 'Connection closed by transport layer',
54
+ 'transport error': 'Transport error occurred',
55
+ 'parse error': 'Error parsing server message',
56
+ 'forced close': 'Connection was forcibly closed',
57
+ 'forced server close': 'Server forcibly closed the connection',
58
+ };
59
+ const description = reasonDescriptions[reason] || 'Unknown disconnect reason';
60
+ runtimeLog.debug(this.label, `Disconnected: ${reason} — ${description}`);
61
+ });
62
+ // ZIG-207: subscribe to the canonical event name. Backend dual-emits
63
+ // `messages` + `chat:message:new` with byte-identical payloads, so
64
+ // listening to ONE of the two is exactly correct — listening to both
65
+ // would double-invoke handleIncomingMessage(). We pick the canonical
66
+ // namespaced name so once the sunset PR drops the legacy `messages`
67
+ // emit, no client change is needed.
68
+ this.socket.on('chat:message:new', async (payload) => {
69
+ try {
70
+ await this.handleIncomingMessage(payload);
71
+ }
72
+ catch (error) {
73
+ const msg = error instanceof Error ? error.message : String(error);
74
+ runtimeLog.error(this.label, `Error handling incoming message: ${msg}`);
75
+ throw error;
76
+ }
77
+ });
78
+ this.socket.on('resource_changed', async (payload) => {
79
+ if (!this.resourceEventHandler)
80
+ return;
81
+ try {
82
+ await this.resourceEventHandler(payload);
83
+ }
84
+ catch (error) {
85
+ const msg = error instanceof Error ? error.message : String(error);
86
+ runtimeLog.error(this.label, `Error handling resource_changed: ${msg}`);
87
+ }
88
+ });
89
+ this.socket.on('error', (error) => {
90
+ runtimeLog.error(this.label, `Socket.IO error: ${String(error)}`);
91
+ });
92
+ }
93
+ connectAsync(timeout = 10_000) {
94
+ if (this.socket?.connected)
95
+ return Promise.resolve(this);
96
+ return new Promise((resolve, reject) => {
97
+ this._connect();
98
+ // Only reject on timeout — not on the first connect_error. socket.io
99
+ // retries automatically (reconnectionAttempts: Infinity), so a single
100
+ // network hiccup or a briefly-down backend shouldn't crash the caller.
101
+ const timer = setTimeout(() => reject(new Error(`[${this.label}] Connection timeout after ${timeout}ms`)), timeout);
102
+ this.socket.once('connect', () => { clearTimeout(timer); resolve(this); });
103
+ });
104
+ }
105
+ disconnect() {
106
+ if (this.socket) {
107
+ this.socket.disconnect();
108
+ this.socket = null;
109
+ }
110
+ }
111
+ send(chatId, receiverId, content, options = {}) {
112
+ if (!chatId || typeof chatId !== 'string')
113
+ throw new Error('send: chatId is required');
114
+ if (!receiverId || typeof receiverId !== 'string')
115
+ throw new Error('send: receiverId is required');
116
+ if (typeof content !== 'string' || content.trim().length === 0)
117
+ throw new Error('send: content must be a non-empty string');
118
+ if (!this.socket)
119
+ throw new Error('send: socket is not initialized. Call connect() first.');
120
+ if (!this.socket.connected)
121
+ throw new Error('send: socket is not connected. Call connect() and wait for connection.');
122
+ const messageId = this.generateMessageId();
123
+ if (!messageId || typeof messageId !== 'string')
124
+ throw new Error('send: failed to generate valid messageId');
125
+ const message = {
126
+ receiverId,
127
+ chatId,
128
+ messageId,
129
+ text: content,
130
+ entryType: options.entryType || 'message',
131
+ content_type: options.content_type || options.contentType || 'text',
132
+ };
133
+ const messagePreview = content.length > 100 ? content.substring(0, 100) + '...' : content;
134
+ // ZIG-207: emit the canonical namespaced inbound event. Backend
135
+ // registers `chat:message:send` as a one-line alias on `handleChat`,
136
+ // so this is byte-identical to the legacy `chat` emit. Streaming
137
+ // chunks keep their existing `chat:chunk` name (already namespaced).
138
+ const event = options.partial ? 'chat:chunk' : 'chat:message:send';
139
+ runtimeLog.debug(this.label, `Sending message (${event}) - chatId: ${chatId}, receiverId: ${receiverId}\n Message: ${messagePreview}`);
140
+ this.socket.emit(event, message);
141
+ }
142
+ isConnected() {
143
+ return this.socket?.connected || false;
144
+ }
145
+ async handleIncomingMessage(payload) {
146
+ if (!payload || typeof payload !== 'object') {
147
+ throw new Error('handleIncomingMessage: payload must be an object');
148
+ }
149
+ const p = payload;
150
+ if (!p['text'] || typeof p['text'] !== 'string' || p['text'].trim().length === 0) {
151
+ throw new Error('handleIncomingMessage: payload.text is required and must be a non-empty string');
152
+ }
153
+ if (!p['chatId'] || typeof p['chatId'] !== 'string') {
154
+ throw new Error('handleIncomingMessage: payload.chatId is required and must be a string');
155
+ }
156
+ const sender = p['sender'];
157
+ const senderId = sender?.['id'];
158
+ const senderType = sender?.['type'];
159
+ if (!senderId || typeof senderId !== 'string') {
160
+ throw new Error('handleIncomingMessage: payload.sender.id is required and must be a string');
161
+ }
162
+ runtimeLog.debug(this.label, `Received message - chatId: ${p['chatId']}, sender: ${senderId} (${senderType})`);
163
+ if (this.agentId && senderId === this.agentId)
164
+ return;
165
+ if (!this.messageHandler) {
166
+ throw new Error('handleIncomingMessage: messageHandler is not set. Call setMessageHandler() first.');
167
+ }
168
+ const chatId = p['chatId'];
169
+ const receiver = p['receiver'];
170
+ const task = p['task'] ?? null;
171
+ const agreement = p['agreement'] ?? null;
172
+ const operation = p['operation'] ?? null;
173
+ const agreementId = p['agreementId'] ?? null;
174
+ runtimeLog.info(this.label, `[wire-debug] incoming chatId=${chatId} sender=${senderId} content_type=${p['content_type'] ?? p['contentType'] ?? '<none>'} entryType=${p['entryType'] ?? '<none>'} task?=${task ? `taskId=${task['taskId']} state=${task['state']}` : 'no'} agreement?=${agreement ? `agreementId=${agreement['agreementId']} status=${agreement['status']}` : 'no'} operation?=${operation ?? 'no'}`);
175
+ const metadata = {
176
+ chatId,
177
+ userId: senderId,
178
+ sender: { id: senderId, type: senderType },
179
+ senderId,
180
+ senderType,
181
+ receiver: receiver || null,
182
+ receiverId: receiver?.['id'] || null,
183
+ entryType: p['entryType'],
184
+ content_type: (p['content_type'] ?? p['contentType']),
185
+ taskId: task?.['taskId'] ?? null,
186
+ task,
187
+ agreement,
188
+ operation,
189
+ agreementId,
190
+ };
191
+ await this.messageHandler(p['text'], metadata);
192
+ }
193
+ buildSocketOptions() {
194
+ // `forceNew: true` is required when multiple WebSocketClient instances
195
+ // hit the same URL with DIFFERENT auth. socket.io's default
196
+ // `multiplex: true` shares the engine.io Manager across `io()` calls
197
+ // to the same URL — and the Manager's auth is fixed by whoever opened
198
+ // it first. So a second client (e.g. an eval's user-mode connection
199
+ // alongside agent-host operator-key connections) would silently inherit
200
+ // the first client's auth and get rejected. Forcing a fresh Manager
201
+ // per client avoids that aliasing.
202
+ const options = {
203
+ transports: ['websocket'],
204
+ reconnection: true,
205
+ reconnectionAttempts: Infinity,
206
+ reconnectionDelay: 1000,
207
+ reconnectionDelayMax: 30000,
208
+ randomizationFactor: 0.5,
209
+ timeout: 20000,
210
+ autoConnect: true,
211
+ forceNew: true,
212
+ multiplex: false,
213
+ };
214
+ if (this.userToken) {
215
+ const bearer = `Bearer ${this.userToken}`;
216
+ options.auth = { token: bearer };
217
+ options.extraHeaders = { Authorization: bearer };
218
+ // No agentId — the gateway sees the `userId` claim and routes as a human user.
219
+ }
220
+ else if (this.operatorKey) {
221
+ const bearer = `Bearer ${this.operatorKey}`;
222
+ options.auth = { token: bearer };
223
+ options.extraHeaders = { Authorization: bearer };
224
+ // agentId goes in query for fleet keys; agent-scoped keys omit it (ZIG-279).
225
+ if (this.agentId)
226
+ options.query = { agentId: this.agentId };
227
+ }
228
+ return options;
229
+ }
230
+ generateMessageId() {
231
+ return crypto.randomUUID();
232
+ }
233
+ }
@@ -0,0 +1 @@
1
+ export { WebSocketClient } from './WebSocketClient.js';
package/package.json CHANGED
@@ -1,26 +1,39 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
- "main": "src/index.js",
6
+ "main": "src/index.ts",
7
7
  "exports": {
8
- ".": "./src/index.js",
9
- "./http": "./src/http/index.js",
10
- "./websocket": "./src/websocket/index.js"
8
+ ".": "./src/index.ts",
9
+ "./http": "./src/http/index.ts",
10
+ "./websocket": "./src/websocket/index.ts"
11
11
  },
12
12
  "engines": {
13
13
  "node": ">=18"
14
14
  },
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "prepack": "npm run build",
18
+ "test": "node --import tsx/esm --test --test-reporter=spec test/*.test.ts",
19
+ "test:watch": "node --import tsx/esm --test --watch test/*.test.ts"
20
+ },
15
21
  "dependencies": {
16
22
  "dotenv": "^17.2.3",
17
23
  "socket.io-client": "^4.7.0"
18
24
  },
19
25
  "keywords": ["ziggs", "api", "client", "websocket"],
20
26
  "license": "MIT",
21
- "files": ["src", "README.md"],
27
+ "files": ["dist", "src", "README.md"],
22
28
  "publishConfig": {
23
29
  "access": "public",
24
- "registry": "https://registry.npmjs.org/"
30
+ "registry": "https://registry.npmjs.org/",
31
+ "main": "dist/index.js",
32
+ "types": "dist/index.d.ts",
33
+ "exports": {
34
+ ".": "./dist/index.js",
35
+ "./http": "./dist/http/index.js",
36
+ "./websocket": "./dist/websocket/index.js"
37
+ }
25
38
  }
26
39
  }