@ziggs-ai/api-client 0.1.8 → 0.1.10

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.
@@ -26,8 +26,12 @@ export interface ContextReadEnvelope<T = unknown> {
26
26
  */
27
27
  export declare class ContextReadClient {
28
28
  private readonly operatorKey;
29
- private readonly agentId;
29
+ private readonly agentId?;
30
30
  private readonly baseUrl;
31
- constructor(operatorKey: string, agentId: string, baseUrl?: string);
31
+ /**
32
+ * @param operatorKey Agent-scoped or fleet operator key.
33
+ * @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
34
+ */
35
+ constructor(operatorKey: string, agentId?: string, baseUrl?: string);
32
36
  read<T = unknown>(type: ContextReadType, query: ContextReadQuery): Promise<ContextReadEnvelope<T>>;
33
37
  }
@@ -8,12 +8,13 @@ export class ContextReadClient {
8
8
  operatorKey;
9
9
  agentId;
10
10
  baseUrl;
11
+ /**
12
+ * @param operatorKey Agent-scoped or fleet operator key.
13
+ * @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
14
+ */
11
15
  constructor(operatorKey, agentId, baseUrl) {
12
16
  if (!operatorKey)
13
17
  throw new Error('ContextReadClient: operatorKey is required');
14
- if (!agentId) {
15
- throw new Error('ContextReadClient: agentId is required (operator-token impersonation)');
16
- }
17
18
  this.operatorKey = operatorKey;
18
19
  this.agentId = agentId;
19
20
  this.baseUrl = baseUrl || getBackendUrl();
@@ -39,8 +40,9 @@ export class ContextReadClient {
39
40
  }
40
41
  const headers = {
41
42
  Authorization: `Bearer ${this.operatorKey}`,
42
- 'X-Agent-Id': this.agentId,
43
43
  };
44
+ if (this.agentId)
45
+ headers['X-Agent-Id'] = this.agentId;
44
46
  if (query.contextGrantId) {
45
47
  headers['X-Context-Grant-Id'] = query.contextGrantId;
46
48
  }
@@ -0,0 +1,66 @@
1
+ import 'dotenv/config';
2
+ export type InboxScopeKind = 'chat' | 'agreement' | 'org';
3
+ export interface InboxScopeEntry {
4
+ scope: {
5
+ kind: InboxScopeKind;
6
+ id: string;
7
+ };
8
+ newMessages: number;
9
+ newArtifacts: number;
10
+ latestAt: string | null;
11
+ since: string;
12
+ }
13
+ export interface InboxProposalRef {
14
+ agreementId: string;
15
+ title: string;
16
+ proposedAt: string | null;
17
+ }
18
+ /** Pull-only MCP: prompt the human when proposals need a decision (ZIG-482). */
19
+ export interface InboxHumanAttention {
20
+ required: true;
21
+ reason: 'proposals_awaiting_me';
22
+ proposalCount: number;
23
+ truncatedProposals: number;
24
+ promptUser: string;
25
+ }
26
+ export interface InboxEnvelope {
27
+ asOf: string;
28
+ scopes: InboxScopeEntry[];
29
+ truncatedScopes: number;
30
+ countCap: number;
31
+ proposalsAwaitingMe: InboxProposalRef[];
32
+ truncatedProposals: number;
33
+ humanAttention?: InboxHumanAttention;
34
+ }
35
+ export interface InboxAck {
36
+ kind: InboxScopeKind;
37
+ id: string;
38
+ upTo: string;
39
+ }
40
+ export interface InboxAckResult {
41
+ acked: Array<{
42
+ scope: {
43
+ kind: InboxScopeKind;
44
+ id: string;
45
+ };
46
+ ackedUpTo: string;
47
+ }>;
48
+ }
49
+ /**
50
+ * The doorbell, not the door (ZIG-434): references and counts since the
51
+ * agent's last ack — never content. Flow: inbox → read → act → ack (ZIG-446).
52
+ * Wraps `GET /agent-api/v1/inbox` and `POST /agent-api/v1/inbox/ack`.
53
+ */
54
+ export declare class InboxClient {
55
+ private readonly operatorKey;
56
+ private readonly agentId?;
57
+ private readonly baseUrl;
58
+ /**
59
+ * @param operatorKey Agent-scoped or fleet operator key.
60
+ * @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
61
+ */
62
+ constructor(operatorKey: string, agentId?: string, baseUrl?: string);
63
+ private headers;
64
+ getInbox(): Promise<InboxEnvelope>;
65
+ ack(scopes: InboxAck[]): Promise<InboxAckResult>;
66
+ }
@@ -0,0 +1,56 @@
1
+ import 'dotenv/config';
2
+ import { getBackendUrl } from '../utils/urlUtils.js';
3
+ /**
4
+ * The doorbell, not the door (ZIG-434): references and counts since the
5
+ * agent's last ack — never content. Flow: inbox → read → act → ack (ZIG-446).
6
+ * Wraps `GET /agent-api/v1/inbox` and `POST /agent-api/v1/inbox/ack`.
7
+ */
8
+ export class InboxClient {
9
+ operatorKey;
10
+ agentId;
11
+ baseUrl;
12
+ /**
13
+ * @param operatorKey Agent-scoped or fleet operator key.
14
+ * @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
15
+ */
16
+ constructor(operatorKey, agentId, baseUrl) {
17
+ if (!operatorKey)
18
+ throw new Error('InboxClient: operatorKey is required');
19
+ this.operatorKey = operatorKey;
20
+ this.agentId = agentId;
21
+ this.baseUrl = baseUrl || getBackendUrl();
22
+ }
23
+ headers() {
24
+ const headers = {
25
+ Authorization: `Bearer ${this.operatorKey}`,
26
+ 'Content-Type': 'application/json',
27
+ };
28
+ if (this.agentId)
29
+ headers['X-Agent-Id'] = this.agentId;
30
+ return headers;
31
+ }
32
+ async getInbox() {
33
+ const res = await fetch(`${this.baseUrl}/agent-api/v1/inbox`, {
34
+ headers: this.headers(),
35
+ });
36
+ const body = await res.text().catch(() => '');
37
+ if (!res.ok) {
38
+ throw new Error(`InboxClient.getInbox ${res.status} ${body.slice(0, 200)}`);
39
+ }
40
+ return JSON.parse(body);
41
+ }
42
+ async ack(scopes) {
43
+ if (!scopes.length)
44
+ throw new Error('InboxClient.ack: scopes are required');
45
+ const res = await fetch(`${this.baseUrl}/agent-api/v1/inbox/ack`, {
46
+ method: 'POST',
47
+ headers: this.headers(),
48
+ body: JSON.stringify({ scopes }),
49
+ });
50
+ const body = await res.text().catch(() => '');
51
+ if (!res.ok) {
52
+ throw new Error(`InboxClient.ack ${res.status} ${body.slice(0, 200)}`);
53
+ }
54
+ return JSON.parse(body);
55
+ }
56
+ }
@@ -16,3 +16,5 @@ export { ContextGrantsClient } from './ContextGrantsClient.js';
16
16
  export type { ContextGrantRecord, ContextGrantScope, ContextGrantScopeKind, ContextTemporal, IssueContextGrantInput, DelegateContextGrantInput, } from './ContextGrantsClient.js';
17
17
  export { AgentSearchClient } from './AgentSearchClient.js';
18
18
  export { TelemetryClient } from './TelemetryClient.js';
19
+ export { InboxClient } from './InboxClient.js';
20
+ export type { InboxScopeKind, InboxScopeEntry, InboxProposalRef, InboxEnvelope, InboxAck, InboxAckResult, } from './InboxClient.js';
@@ -10,3 +10,4 @@ export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
10
10
  export { ContextGrantsClient } from './ContextGrantsClient.js';
11
11
  export { AgentSearchClient } from './AgentSearchClient.js';
12
12
  export { TelemetryClient } from './TelemetryClient.js';
13
+ export { InboxClient } from './InboxClient.js';
package/dist/types.d.ts CHANGED
@@ -99,6 +99,8 @@ export declare function isValidContentType(contentType: string, entryType: strin
99
99
  export declare const OPEN_AGREEMENT_TARGET: "everyone";
100
100
  export interface MessageMetadata {
101
101
  chatId: string;
102
+ /** Stable id for dedup across push + inbox catch-up (ZIG-454). */
103
+ messageId?: string;
102
104
  userId: string;
103
105
  sender: {
104
106
  id: string;
@@ -41,6 +41,8 @@ export interface SendOptions {
41
41
  * Requires backend streaming support.
42
42
  */
43
43
  partial?: boolean;
44
+ /** Pre-allocated messageId for streaming continuity — links chunks to the final message. */
45
+ messageId?: string;
44
46
  }
45
47
  export declare class WebSocketClient {
46
48
  private wsUrl;
@@ -60,6 +62,13 @@ export declare class WebSocketClient {
60
62
  * `event.kind` + ids. Replaces cursor-based polling for delta detection.
61
63
  */
62
64
  setResourceEventHandler(handler: ResourceEventHandler): void;
65
+ private connectHandlers;
66
+ /**
67
+ * Called on every socket connect, including socket.io reconnects.
68
+ * Used for inbox catch-up (ZIG-454) so missed pushes are recovered from MongoDB.
69
+ */
70
+ onConnect(handler: () => void | Promise<void>): void;
71
+ private _emitConnect;
63
72
  private _connect;
64
73
  connectAsync(timeout?: number): Promise<this>;
65
74
  disconnect(): void;
@@ -33,6 +33,25 @@ export class WebSocketClient {
33
33
  setResourceEventHandler(handler) {
34
34
  this.resourceEventHandler = handler;
35
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
+ }
36
55
  _connect() {
37
56
  if (this.socket?.connected)
38
57
  return;
@@ -41,6 +60,7 @@ export class WebSocketClient {
41
60
  this.socket = io(this.wsUrl, socketOptions);
42
61
  this.socket.on('connect', () => {
43
62
  runtimeLog.info(this.label, 'Connected');
63
+ void this._emitConnect();
44
64
  });
45
65
  this.socket.on('connect_error', (error) => {
46
66
  runtimeLog.error(this.label, `Connection error: ${error.message}`);
@@ -119,7 +139,7 @@ export class WebSocketClient {
119
139
  throw new Error('send: socket is not initialized. Call connect() first.');
120
140
  if (!this.socket.connected)
121
141
  throw new Error('send: socket is not connected. Call connect() and wait for connection.');
122
- const messageId = this.generateMessageId();
142
+ const messageId = options.messageId ?? this.generateMessageId();
123
143
  if (!messageId || typeof messageId !== 'string')
124
144
  throw new Error('send: failed to generate valid messageId');
125
145
  const message = {
@@ -174,6 +194,7 @@ export class WebSocketClient {
174
194
  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
195
  const metadata = {
176
196
  chatId,
197
+ messageId: typeof p['messageId'] === 'string' ? p['messageId'] : undefined,
177
198
  userId: senderId,
178
199
  sender: { id: senderId, type: senderType },
179
200
  senderId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",