hiloop-sdk 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { HiloopClient, HiloopError } from "./client.js";
2
2
  export type { HiloopClientOptions } from "./client.js";
3
+ export { HiloopWsClient } from "./ws.js";
4
+ export type { HiloopWsClientOptions, WsEventType, WsEventHandler } from "./ws.js";
3
5
  export { generateKeyPair, deriveKeyPairFromApiKey, computeFingerprint, encrypt, decrypt, encryptAes, decryptAes, deriveWrappingKey, CryptoContext } from "./crypto.js";
4
6
  export type { KeyPair, ContentWrappingResult } from "./crypto.js";
5
7
  export type { Interaction, InteractionType, InteractionStatus, Priority, Message, PaginatedResult, CreateInteractionOptions, ConvSession, ConvSessionType, ConvSessionStatus, ConvSessionRole, ConvSessionParticipant, ConvSessionMessage, GuestToken, CreateConvSessionOptions, Channel, ChannelStatus, ChannelMessage, ChannelParticipant, CreateChannelOptions, FileChangeStatus, CommandInvocationStatus, AgentCommand, CommandInvocation, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { HiloopClient, HiloopError } from "./client.js";
2
+ export { HiloopWsClient } from "./ws.js";
2
3
  export { generateKeyPair, deriveKeyPairFromApiKey, computeFingerprint, encrypt, decrypt, encryptAes, decryptAes, deriveWrappingKey, CryptoContext } from "./crypto.js";
3
4
  export { parseInteraction, parseMessage, parseConvSession, parseConvSessionMessage, parseGuestToken, parseChannel, parseChannelMessage, parseChannelParticipant, } from "./types.js";
package/dist/ws.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * HiloopWsClient — real-time WebSocket client for agents.
3
+ *
4
+ * Usage:
5
+ * const ws = new HiloopWsClient({
6
+ * apiKey: "hlp_space_...",
7
+ * agentName: "my-bot",
8
+ * });
9
+ * ws.on("session.message.new", (msg) => console.log(msg.content));
10
+ * await ws.connect();
11
+ * ws.subscribe(`session:${sessionId}`);
12
+ * ws.sendSessionMessage(sessionId, "Hello!");
13
+ */
14
+ export interface HiloopWsClientOptions {
15
+ apiKey: string;
16
+ /** Agent name (required for space API keys, auto-creates if needed). */
17
+ agentName?: string;
18
+ baseUrl?: string;
19
+ /** Auto-reconnect on disconnect (default: true). */
20
+ autoReconnect?: boolean;
21
+ /** Max reconnect delay in ms (default: 30000). */
22
+ maxReconnectDelay?: number;
23
+ }
24
+ export type WsEventType = "connected" | "session.message.new" | "session.message.sent" | "interaction.created" | "interaction.sent" | "interaction.updated" | "interaction.message.new" | "typing.start" | "typing.stop" | "session.closed" | "session.member.joined" | "session.member.left" | "subscribed" | "unsubscribed" | "error" | "pong";
25
+ export type WsEventHandler = (data: any) => void;
26
+ export declare class HiloopWsClient {
27
+ private ws;
28
+ private options;
29
+ private agentId;
30
+ private handlers;
31
+ private anyHandlers;
32
+ private reconnectAttempts;
33
+ private reconnectTimer;
34
+ private closed;
35
+ private crypto;
36
+ private initPromise;
37
+ constructor(options: HiloopWsClientOptions);
38
+ /** Initialize encryption context (derives keypair from API key, fetches space key). */
39
+ private ensureCrypto;
40
+ private doInitCrypto;
41
+ /** Connect to the agent WebSocket. Returns when connected. */
42
+ connect(): Promise<void>;
43
+ /** Disconnect and stop reconnecting. */
44
+ disconnect(): void;
45
+ /** Subscribe to a topic (e.g., `session:<id>`, `agent:<id>`, `space:<id>`). */
46
+ subscribe(topic: string): void;
47
+ /** Unsubscribe from a topic. */
48
+ unsubscribe(topic: string): void;
49
+ /** Send a session message (auto-encrypts). */
50
+ sendSessionMessage(sessionId: string, content: string): Promise<void>;
51
+ /** Send a typing indicator. */
52
+ sendTyping(sessionId: string, isTyping: boolean): void;
53
+ /** Listen for a specific event type. */
54
+ on(eventType: WsEventType | string, handler: WsEventHandler): () => void;
55
+ /** Listen for all events. */
56
+ onAny(handler: WsEventHandler): () => void;
57
+ /** Remove all listeners for an event type. */
58
+ off(eventType: string): void;
59
+ /** Auto-decrypt a session message's encryptedContent field. */
60
+ decryptSessionMessage(encryptedContent: string): Promise<string | null>;
61
+ private send;
62
+ private emit;
63
+ private scheduleReconnect;
64
+ }
package/dist/ws.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * HiloopWsClient — real-time WebSocket client for agents.
3
+ *
4
+ * Usage:
5
+ * const ws = new HiloopWsClient({
6
+ * apiKey: "hlp_space_...",
7
+ * agentName: "my-bot",
8
+ * });
9
+ * ws.on("session.message.new", (msg) => console.log(msg.content));
10
+ * await ws.connect();
11
+ * ws.subscribe(`session:${sessionId}`);
12
+ * ws.sendSessionMessage(sessionId, "Hello!");
13
+ */
14
+ import { CryptoContext, deriveKeyPairFromApiKey, computeFingerprint } from "./crypto.js";
15
+ export class HiloopWsClient {
16
+ constructor(options) {
17
+ this.ws = null;
18
+ this.agentId = null;
19
+ this.handlers = new Map();
20
+ this.anyHandlers = new Set();
21
+ this.reconnectAttempts = 0;
22
+ this.reconnectTimer = null;
23
+ this.closed = false;
24
+ this.crypto = null;
25
+ this.initPromise = null;
26
+ this.options = options;
27
+ }
28
+ /** Initialize encryption context (derives keypair from API key, fetches space key). */
29
+ async ensureCrypto() {
30
+ if (this.crypto)
31
+ return;
32
+ if (this.initPromise)
33
+ return this.initPromise;
34
+ this.initPromise = this.doInitCrypto();
35
+ await this.initPromise;
36
+ }
37
+ async doInitCrypto() {
38
+ const kp = await deriveKeyPairFromApiKey(this.options.apiKey);
39
+ const baseUrl = (this.options.baseUrl ?? "https://api.hi-loop.com").replace(/\/$/, "");
40
+ const headers = {
41
+ "X-API-Key": this.options.apiKey,
42
+ "Content-Type": "application/json",
43
+ };
44
+ if (this.options.agentName)
45
+ headers["X-Agent"] = this.options.agentName;
46
+ // Register public key
47
+ const fingerprint = await computeFingerprint(kp.publicKey);
48
+ try {
49
+ await fetch(`${baseUrl}/v1/agent/keys/me`, { method: "PUT", headers, body: JSON.stringify({ publicKey: kp.publicKey, fingerprint }) });
50
+ }
51
+ catch { /* non-fatal */ }
52
+ // Fetch space encryption info
53
+ let spacePublicKey = "";
54
+ let spaceKeyId = "";
55
+ try {
56
+ const res = await fetch(`${baseUrl}/v1/agent/encryption-info`, { headers });
57
+ if (res.ok) {
58
+ const info = await res.json();
59
+ spacePublicKey = info.spacePublicKey;
60
+ spaceKeyId = info.spaceKeyId;
61
+ }
62
+ }
63
+ catch { /* non-fatal */ }
64
+ this.crypto = new CryptoContext(kp.secretKey, kp.publicKey, spacePublicKey, spaceKeyId);
65
+ }
66
+ /** Connect to the agent WebSocket. Returns when connected. */
67
+ async connect() {
68
+ await this.ensureCrypto();
69
+ this.closed = false;
70
+ return new Promise((resolve, reject) => {
71
+ const baseUrl = (this.options.baseUrl ?? "https://api.hi-loop.com").replace(/\/$/, "");
72
+ const wsUrl = baseUrl.replace(/^http/, "ws");
73
+ const params = new URLSearchParams({ key: this.options.apiKey });
74
+ if (this.options.agentName)
75
+ params.set("agent", this.options.agentName);
76
+ this.ws = new WebSocket(`${wsUrl}/v1/agent/ws?${params}`);
77
+ this.ws.onopen = () => {
78
+ this.reconnectAttempts = 0;
79
+ };
80
+ this.ws.onmessage = (event) => {
81
+ try {
82
+ const data = JSON.parse(event.data);
83
+ if (data.type === "connected") {
84
+ this.agentId = data.agentId;
85
+ resolve();
86
+ }
87
+ // Skip metadata-only message.new (agents get the richer session.message.new instead)
88
+ if (data.type === "message.new") {
89
+ return;
90
+ }
91
+ // Skip echoes: don't emit session messages sent by this agent
92
+ if (data.type === "session.message.new" && data.senderType === "agent" && data.senderId === this.agentId) {
93
+ return;
94
+ }
95
+ this.emit(data.type, data);
96
+ }
97
+ catch { /* ignore malformed */ }
98
+ };
99
+ this.ws.onclose = () => {
100
+ if (!this.closed && (this.options.autoReconnect ?? true)) {
101
+ this.scheduleReconnect();
102
+ }
103
+ };
104
+ this.ws.onerror = (err) => {
105
+ if (this.reconnectAttempts === 0) {
106
+ reject(err);
107
+ }
108
+ };
109
+ });
110
+ }
111
+ /** Disconnect and stop reconnecting. */
112
+ disconnect() {
113
+ this.closed = true;
114
+ if (this.reconnectTimer) {
115
+ clearTimeout(this.reconnectTimer);
116
+ this.reconnectTimer = null;
117
+ }
118
+ if (this.ws) {
119
+ this.ws.close();
120
+ this.ws = null;
121
+ }
122
+ }
123
+ /** Subscribe to a topic (e.g., `session:<id>`, `agent:<id>`, `space:<id>`). */
124
+ subscribe(topic) {
125
+ this.send({ type: "subscribe", topic });
126
+ }
127
+ /** Unsubscribe from a topic. */
128
+ unsubscribe(topic) {
129
+ this.send({ type: "unsubscribe", topic });
130
+ }
131
+ /** Send a session message (auto-encrypts). */
132
+ async sendSessionMessage(sessionId, content) {
133
+ await this.ensureCrypto();
134
+ const encryptedContent = await this.crypto.encryptSessionMessage(content);
135
+ this.send({ type: "session.send", sessionId, encryptedContent });
136
+ }
137
+ /** Send a typing indicator. */
138
+ sendTyping(sessionId, isTyping) {
139
+ this.send({ type: "typing", sessionId, isTyping });
140
+ }
141
+ /** Listen for a specific event type. */
142
+ on(eventType, handler) {
143
+ let handlers = this.handlers.get(eventType);
144
+ if (!handlers) {
145
+ handlers = new Set();
146
+ this.handlers.set(eventType, handlers);
147
+ }
148
+ handlers.add(handler);
149
+ return () => { handlers.delete(handler); };
150
+ }
151
+ /** Listen for all events. */
152
+ onAny(handler) {
153
+ this.anyHandlers.add(handler);
154
+ return () => { this.anyHandlers.delete(handler); };
155
+ }
156
+ /** Remove all listeners for an event type. */
157
+ off(eventType) {
158
+ this.handlers.delete(eventType);
159
+ }
160
+ /** Auto-decrypt a session message's encryptedContent field. */
161
+ async decryptSessionMessage(encryptedContent) {
162
+ await this.ensureCrypto();
163
+ return this.crypto.decryptSessionMessage(encryptedContent);
164
+ }
165
+ // -- Private ----------------------------------------------------------------
166
+ send(data) {
167
+ if (this.ws?.readyState === WebSocket.OPEN) {
168
+ this.ws.send(JSON.stringify(data));
169
+ }
170
+ }
171
+ emit(eventType, data) {
172
+ const handlers = this.handlers.get(eventType);
173
+ if (handlers) {
174
+ for (const h of handlers) {
175
+ try {
176
+ h(data);
177
+ }
178
+ catch { /* don't break event loop */ }
179
+ }
180
+ }
181
+ for (const h of this.anyHandlers) {
182
+ try {
183
+ h(data);
184
+ }
185
+ catch { /* don't break event loop */ }
186
+ }
187
+ }
188
+ scheduleReconnect() {
189
+ const maxDelay = this.options.maxReconnectDelay ?? 30000;
190
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), maxDelay);
191
+ this.reconnectAttempts++;
192
+ this.reconnectTimer = setTimeout(() => {
193
+ this.connect().catch(() => {
194
+ // reconnect will retry via onclose
195
+ });
196
+ }, delay);
197
+ }
198
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hiloop-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "TypeScript SDK for Hiloop — zero-trust human-AI agent interaction platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",