hiloop-sdk 0.8.5 → 0.8.7

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/client.d.ts CHANGED
@@ -5,6 +5,16 @@ export declare class HiloopError extends Error {
5
5
  statusCode: number;
6
6
  constructor(statusCode: number, message: string);
7
7
  }
8
+ export interface SendOptions {
9
+ agentName?: string;
10
+ priority?: string;
11
+ deadlineMinutes?: number;
12
+ replyToMessageId?: string;
13
+ /** Client-supplied correlation key (e.g. tool_use_id). Unique per session. */
14
+ referenceId?: string;
15
+ /** Resolve a referenceId to a message and set replyToMessageId automatically. */
16
+ replyToReferenceId?: string;
17
+ }
8
18
  export interface HiloopClientOptions {
9
19
  apiKey: string;
10
20
  /** Agent name. Auto-creates the agent if it doesn't exist (space API keys only). */
@@ -137,19 +147,19 @@ export declare class HiloopClient {
137
147
  page?: number;
138
148
  }): Promise<ConvSession[]>;
139
149
  closeConvSession(sessionId: string): Promise<ConvSession>;
150
+ /** Options for send / sendText. */
151
+ private sendComponents;
140
152
  /**
141
- * Send a message to a session. Accepts plaintext content and optional
142
- * rich components. Encrypts content and components automatically.
143
- *
144
- * This is the primary method for agent-to-human communication.
153
+ * Send a plain text message. Wraps the text in a `{ type: "text" }` component.
154
+ * Agent and session are auto-created if they don't exist.
145
155
  */
146
- sendMessage(sessionId: string, content: string, opts?: {
147
- components?: ComponentNode[];
148
- priority?: string;
149
- deadlineMinutes?: number;
150
- replyToMessageId?: string;
151
- agentName?: string;
152
- }): Promise<SessionMessage>;
156
+ sendText(sessionId: string, text: string, opts?: SendOptions): Promise<SessionMessage>;
157
+ /**
158
+ * Send a message composed of components. Everything is encrypted in the
159
+ * component tree — no separate content field.
160
+ * Agent and session are auto-created if they don't exist.
161
+ */
162
+ send(sessionId: string, components: ComponentNode[], opts?: SendOptions): Promise<SessionMessage>;
153
163
  /**
154
164
  * Long-poll for a response to a specific message.
155
165
  * Returns the updated message once the human has responded, or after timeout.
@@ -340,11 +350,12 @@ export declare class HiloopClient {
340
350
  decryptSessionMessage(encryptedContent: string): Promise<string | null>;
341
351
  /**
342
352
  * Decrypt a webhook payload in place.
343
- * Decrypts encryptedContent from session messages.
344
- * Returns the payload with an added `content` field.
353
+ * Decrypts encryptedComponents (and legacy encryptedContent) from session messages.
354
+ * Returns the payload with added `content` and `components` fields.
345
355
  */
346
356
  decryptWebhookPayload<T extends Record<string, unknown>>(payload: T): Promise<T & {
347
357
  content?: string;
358
+ components?: ComponentNode[];
348
359
  }>;
349
360
  static generateKeyPair(): KeyPair;
350
361
  /** Encrypt plaintext with AES-256-GCM for session messages. */
package/dist/client.js CHANGED
@@ -317,25 +317,17 @@ export class HiloopClient {
317
317
  return parseConvSession(data);
318
318
  }
319
319
  // -- Unified Message API ----------------------------------------------------
320
- /**
321
- * Send a message to a session. Accepts plaintext content and optional
322
- * rich components. Encrypts content and components automatically.
323
- *
324
- * This is the primary method for agent-to-human communication.
325
- */
326
- async sendMessage(sessionId, content, opts) {
320
+ /** Options for send / sendText. */
321
+ async sendComponents(sessionId, components, opts) {
327
322
  await this.ensureInitialized(opts?.agentName);
328
- const encryptedContent = await this.crypto.encryptSessionMessage(content);
329
- const body = { encryptedContent };
330
- if (opts?.components && opts.components.length > 0) {
331
- body.encryptedComponents = await this.crypto.encryptSessionMessage(JSON.stringify(opts.components));
332
- // Extract metadata from components for server-side routing
333
- const hasResponseRequired = opts.components.some((n) => n.type === "button_group" &&
334
- n.data?.responseRequired === true);
335
- if (hasResponseRequired) {
336
- body.responseRequired = true;
337
- body.category = "decide";
338
- }
323
+ const encryptedComponents = await this.crypto.encryptSessionMessage(JSON.stringify(components));
324
+ const body = { encryptedComponents };
325
+ // Extract metadata from components for server-side routing
326
+ const hasResponseRequired = components.some((n) => n.type === "button_group" &&
327
+ n.data?.responseRequired === true);
328
+ if (hasResponseRequired) {
329
+ body.responseRequired = true;
330
+ body.category = "decide";
339
331
  }
340
332
  if (opts?.priority)
341
333
  body.priority = opts.priority;
@@ -343,9 +335,28 @@ export class HiloopClient {
343
335
  body.deadlineMinutes = opts.deadlineMinutes;
344
336
  if (opts?.replyToMessageId)
345
337
  body.replyToMessageId = opts.replyToMessageId;
338
+ if (opts?.referenceId)
339
+ body.referenceId = opts.referenceId;
340
+ if (opts?.replyToReferenceId)
341
+ body.replyToReferenceId = opts.replyToReferenceId;
346
342
  const data = await this.request("POST", `/agent/sessions/${sessionId}/messages`, { body, agentName: opts?.agentName });
347
343
  return parseSessionMessage(data);
348
344
  }
345
+ /**
346
+ * Send a plain text message. Wraps the text in a `{ type: "text" }` component.
347
+ * Agent and session are auto-created if they don't exist.
348
+ */
349
+ async sendText(sessionId, text, opts) {
350
+ return this.sendComponents(sessionId, [{ type: "text", data: { content: text } }], opts);
351
+ }
352
+ /**
353
+ * Send a message composed of components. Everything is encrypted in the
354
+ * component tree — no separate content field.
355
+ * Agent and session are auto-created if they don't exist.
356
+ */
357
+ async send(sessionId, components, opts) {
358
+ return this.sendComponents(sessionId, components, opts);
359
+ }
349
360
  /**
350
361
  * Long-poll for a response to a specific message.
351
362
  * Returns the updated message once the human has responded, or after timeout.
@@ -396,7 +407,25 @@ export class HiloopClient {
396
407
  // Decrypt timeline items (session messages)
397
408
  if (Array.isArray(result.items)) {
398
409
  await Promise.all(result.items.map(async (item) => {
399
- if (typeof item.encryptedContent === "string") {
410
+ // Decrypt components (new format)
411
+ if (typeof item.encryptedComponents === "string") {
412
+ const plain = await this.crypto.decryptSessionMessage(item.encryptedComponents);
413
+ if (plain !== null) {
414
+ try {
415
+ const nodes = JSON.parse(plain);
416
+ item.components = nodes;
417
+ // Extract text from first text component for convenience
418
+ const textNode = nodes.find((n) => n.type === "text");
419
+ if (textNode)
420
+ item.content = textNode.data?.content ?? null;
421
+ }
422
+ catch {
423
+ item.components = [];
424
+ }
425
+ }
426
+ }
427
+ // Fall back to legacy encryptedContent
428
+ if (item.content === undefined && typeof item.encryptedContent === "string") {
400
429
  const plain = await this.crypto.decryptSessionMessage(item.encryptedContent);
401
430
  if (plain !== null)
402
431
  item.content = plain;
@@ -660,14 +689,28 @@ export class HiloopClient {
660
689
  }
661
690
  /**
662
691
  * Decrypt a webhook payload in place.
663
- * Decrypts encryptedContent from session messages.
664
- * Returns the payload with an added `content` field.
692
+ * Decrypts encryptedComponents (and legacy encryptedContent) from session messages.
693
+ * Returns the payload with added `content` and `components` fields.
665
694
  */
666
695
  async decryptWebhookPayload(payload) {
667
696
  await this.ensureInitialized();
668
697
  const result = { ...payload };
669
- // Session message
670
- if (typeof payload.encryptedContent === "string") {
698
+ // New format: components
699
+ if (typeof payload.encryptedComponents === "string") {
700
+ const plain = await this.crypto.decryptSessionMessage(payload.encryptedComponents);
701
+ if (plain !== null) {
702
+ try {
703
+ const nodes = JSON.parse(plain);
704
+ result.components = nodes;
705
+ const textNode = nodes.find((n) => n.type === "text");
706
+ if (textNode)
707
+ result.content = textNode.data?.content ?? undefined;
708
+ }
709
+ catch { /* ignore */ }
710
+ }
711
+ }
712
+ // Legacy: encryptedContent
713
+ if (result.content === undefined && typeof payload.encryptedContent === "string") {
671
714
  const plain = await this.crypto.decryptSessionMessage(payload.encryptedContent);
672
715
  if (plain !== null)
673
716
  result.content = plain;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { HiloopClient, HiloopError } from "./client.js";
2
- export type { HiloopClientOptions } from "./client.js";
2
+ export type { HiloopClientOptions, SendOptions } from "./client.js";
3
3
  export { HiloopWsClient } from "./ws.js";
4
4
  export type { HiloopWsClientOptions, WsEventType, WsEventHandler } from "./ws.js";
5
5
  export { generateKeyPair, deriveKeyPairFromApiKey, computeFingerprint, encrypt, decrypt, encryptAes, decryptAes, deriveWrappingKey, CryptoContext } from "./crypto.js";
package/dist/types.d.ts CHANGED
@@ -139,6 +139,7 @@ export interface SessionMessage {
139
139
  priority?: string | null;
140
140
  status?: string | null;
141
141
  deadlineAt?: string | null;
142
+ referenceId?: string | null;
142
143
  replyToMessageId?: string | null;
143
144
  respondedAt?: string | null;
144
145
  viewedAt?: string | null;
package/dist/types.js CHANGED
@@ -100,6 +100,7 @@ export function parseSessionMessage(data) {
100
100
  priority: m.priority ?? null,
101
101
  status: m.status ?? null,
102
102
  deadlineAt: m.deadlineAt ?? null,
103
+ referenceId: m.referenceId ?? null,
103
104
  replyToMessageId: m.replyToMessageId ?? null,
104
105
  respondedAt: m.respondedAt ?? null,
105
106
  viewedAt: m.viewedAt ?? null,
package/dist/ws.d.ts CHANGED
@@ -46,7 +46,7 @@ export declare class HiloopWsClient {
46
46
  subscribe(topic: string): void;
47
47
  /** Unsubscribe from a topic. */
48
48
  unsubscribe(topic: string): void;
49
- /** Send a session message (auto-encrypts). */
49
+ /** Send a text session message (auto-encrypts as component). */
50
50
  sendSessionMessage(sessionId: string, content: string): Promise<void>;
51
51
  /** Send a typing indicator. */
52
52
  sendTyping(sessionId: string, isTyping: boolean): void;
package/dist/ws.js CHANGED
@@ -101,10 +101,26 @@ export class HiloopWsClient {
101
101
  }
102
102
  }
103
103
  // Auto-decrypt session messages
104
- if (data.type === "session.message.new" && data.encryptedContent && this.crypto) {
105
- const plain = await this.crypto.decryptSessionMessage(data.encryptedContent);
106
- if (plain !== null)
107
- data.content = plain;
104
+ if (data.type === "session.message.new" && this.crypto) {
105
+ // New format: components
106
+ if (data.encryptedComponents) {
107
+ const plain = await this.crypto.decryptSessionMessage(data.encryptedComponents);
108
+ if (plain !== null) {
109
+ try {
110
+ data.components = JSON.parse(plain);
111
+ const textNode = data.components?.find((n) => n.type === "text");
112
+ if (textNode)
113
+ data.content = textNode.data?.content ?? null;
114
+ }
115
+ catch { /* ignore */ }
116
+ }
117
+ }
118
+ // Legacy: encryptedContent
119
+ if (data.content === undefined && data.encryptedContent) {
120
+ const plain = await this.crypto.decryptSessionMessage(data.encryptedContent);
121
+ if (plain !== null)
122
+ data.content = plain;
123
+ }
108
124
  }
109
125
  this.emit(data.type, data);
110
126
  }
@@ -143,11 +159,12 @@ export class HiloopWsClient {
143
159
  unsubscribe(topic) {
144
160
  this.send({ type: "unsubscribe", topic });
145
161
  }
146
- /** Send a session message (auto-encrypts). */
162
+ /** Send a text session message (auto-encrypts as component). */
147
163
  async sendSessionMessage(sessionId, content) {
148
164
  await this.ensureCrypto();
149
- const encryptedContent = await this.crypto.encryptSessionMessage(content);
150
- this.send({ type: "session.send", sessionId, encryptedContent });
165
+ const components = [{ type: "text", data: { content } }];
166
+ const encryptedComponents = await this.crypto.encryptSessionMessage(JSON.stringify(components));
167
+ this.send({ type: "session.send", sessionId, encryptedComponents });
151
168
  }
152
169
  /** Send a typing indicator. */
153
170
  sendTyping(sessionId, isTyping) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hiloop-sdk",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "TypeScript SDK for Hiloop — zero-trust human-AI agent interaction platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",