@ziggs-ai/api-client 0.1.21 → 0.1.23

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.
@@ -233,81 +233,95 @@ export async function getAgreementStatus(agreementId, creds) {
233
233
  }
234
234
  export async function listAgreements(filters = {}, creds) {
235
235
  assertCreds(creds, 'list agreements');
236
+ // ZIG-699 — return an empty array only for a genuine empty 200 result. Any
237
+ // failure (non-2xx / network) throws so the MCP tool reports a real error
238
+ // instead of "you have no agreements". The status stays whitespace-delimited
239
+ // so the tool's toolError/classifyToolError maps it to a stable code.
240
+ // Canonical query path: GET /agreements?scope=&status=&engagementKind=&...
241
+ const url = new URL(getAgreementBaseUrl());
242
+ if (filters.status)
243
+ url.searchParams.set('status', filters.status);
244
+ if (filters.engagementKind)
245
+ url.searchParams.set('engagementKind', filters.engagementKind);
246
+ if (filters.proposalStatus)
247
+ url.searchParams.set('proposalStatus', filters.proposalStatus);
248
+ if (filters.hasTask != null)
249
+ url.searchParams.set('hasTask', String(filters.hasTask));
250
+ let res;
236
251
  try {
237
- // Canonical query path: GET /agreements?scope=&status=&engagementKind=&...
238
- const url = new URL(getAgreementBaseUrl());
239
- if (filters.status)
240
- url.searchParams.set('status', filters.status);
241
- if (filters.engagementKind)
242
- url.searchParams.set('engagementKind', filters.engagementKind);
243
- if (filters.proposalStatus)
244
- url.searchParams.set('proposalStatus', filters.proposalStatus);
245
- if (filters.hasTask != null)
246
- url.searchParams.set('hasTask', String(filters.hasTask));
247
- const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
248
- if (!res.ok) {
249
- const body = await res.text().catch(() => '');
250
- runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
251
- return [];
252
- }
253
- const data = await res.json().catch(() => null);
254
- return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
252
+ res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
255
253
  }
256
254
  catch (e) {
257
255
  runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${e.message}`);
258
- return [];
256
+ throw new Error(`GET /agreements network error: ${e.message}`);
257
+ }
258
+ if (!res.ok) {
259
+ const body = await res.text().catch(() => '');
260
+ runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
261
+ throw new Error(`GET /agreements ${res.status} ${body?.slice(0, 200)}`);
259
262
  }
263
+ const data = await res.json().catch(() => null);
264
+ return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
260
265
  }
261
266
  export async function getMyAgreements(filters = {}, creds) {
262
267
  assertCreds(creds, 'get my agreements');
268
+ // ZIG-699 — empty array only for a genuine empty 200; any failure throws so
269
+ // the tool surfaces a real error rather than "no agreements" (see listAgreements).
270
+ // Canonical query path: GET /agreements?scope=mine returns the enriched
271
+ // shape (tasks + originChatId + isYou flags).
272
+ const url = new URL(getAgreementBaseUrl());
273
+ url.searchParams.set('scope', 'mine');
274
+ if (filters.engagementKind)
275
+ url.searchParams.set('engagementKind', filters.engagementKind);
276
+ if (filters.proposalStatus)
277
+ url.searchParams.set('proposalStatus', filters.proposalStatus);
278
+ if (filters.hasTask != null)
279
+ url.searchParams.set('hasTask', String(filters.hasTask));
280
+ let res;
263
281
  try {
264
- // Canonical query path: GET /agreements?scope=mine returns the enriched
265
- // shape (tasks + originChatId + isYou flags).
266
- const url = new URL(getAgreementBaseUrl());
267
- url.searchParams.set('scope', 'mine');
268
- if (filters.engagementKind)
269
- url.searchParams.set('engagementKind', filters.engagementKind);
270
- if (filters.proposalStatus)
271
- url.searchParams.set('proposalStatus', filters.proposalStatus);
272
- if (filters.hasTask != null)
273
- url.searchParams.set('hasTask', String(filters.hasTask));
274
- const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
275
- if (!res.ok) {
276
- const body = await res.text().catch(() => '');
277
- runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
278
- return [];
279
- }
280
- const data = await res.json().catch(() => null);
281
- return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
282
+ res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
282
283
  }
283
284
  catch (e) {
284
285
  runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${e.message}`);
285
- return [];
286
+ throw new Error(`GET /agreements?scope=mine network error: ${e.message}`);
286
287
  }
288
+ if (!res.ok) {
289
+ const body = await res.text().catch(() => '');
290
+ runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
291
+ throw new Error(`GET /agreements?scope=mine ${res.status} ${body?.slice(0, 200)}`);
292
+ }
293
+ const data = await res.json().catch(() => null);
294
+ return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
287
295
  }
288
296
  export async function getAgreement(agreementId, creds) {
289
297
  if (!agreementId)
290
298
  return null;
291
299
  assertCreds(creds, 'get agreement');
300
+ // ZIG-698 — a genuine 404 (and 200-with-no-agreement) is a real "not found"
301
+ // and returns null. Every other failure (403/5xx/network) must throw so the
302
+ // caller can tell "does not exist" from "could not fetch" instead of a 403
303
+ // masquerading as not-found. The MCP tool classifies the thrown status via
304
+ // toolError; the message keeps the status whitespace-delimited for that.
305
+ let res;
292
306
  try {
293
- const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}`, {
307
+ res = await fetch(`${getAgreementBaseUrl()}/${agreementId}`, {
294
308
  method: 'GET',
295
309
  headers: buildHeaders(creds),
296
310
  });
297
- if (res.status === 404)
298
- return null;
299
- if (!res.ok) {
300
- const body = await res.text().catch(() => '');
301
- runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
302
- return null;
303
- }
304
- const data = await res.json().catch(() => null);
305
- return data?.['agreement'] ?? null;
306
311
  }
307
312
  catch (e) {
308
313
  runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${e.message}`);
314
+ throw new Error(`GET /agreements/${agreementId} network error: ${e.message}`);
315
+ }
316
+ if (res.status === 404)
309
317
  return null;
318
+ if (!res.ok) {
319
+ const body = await res.text().catch(() => '');
320
+ runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
321
+ throw new Error(`GET /agreements/${agreementId} ${res.status} ${body?.slice(0, 200)}`);
310
322
  }
323
+ const data = await res.json().catch(() => null);
324
+ return data?.['agreement'] ?? null;
311
325
  }
312
326
  export async function createAgreement(body, creds) {
313
327
  if (!body)
@@ -112,23 +112,27 @@ export async function sendChatMessage(input, creds) {
112
112
  }
113
113
  export async function listMyChats(creds) {
114
114
  assertCreds(creds, 'list my chats');
115
+ // ZIG-699 — empty array only for a genuine empty 200; any failure (non-2xx /
116
+ // network) throws so ziggs_list_chats reports a real error instead of "you
117
+ // have no chats". Status stays whitespace-delimited for toolError classification.
118
+ let res;
115
119
  try {
116
- const res = await fetch(`${getBackendUrl()}/chats/mine`, {
120
+ res = await fetch(`${getBackendUrl()}/chats/mine`, {
117
121
  method: 'GET',
118
122
  headers: buildHeaders(creds),
119
123
  });
120
- if (!res.ok) {
121
- const body = await res.text().catch(() => '');
122
- runtimeLog.warn('ChatClient', `⚠️ listMyChats failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
123
- return [];
124
- }
125
- const data = await res.json().catch(() => null);
126
- return Array.isArray(data?.['chats']) ? data['chats'] : [];
127
124
  }
128
125
  catch (e) {
129
126
  runtimeLog.warn('ChatClient', `⚠️ listMyChats failed: ${e.message}`);
130
- return [];
127
+ throw new Error(`GET /chats/mine network error: ${e.message}`);
131
128
  }
129
+ if (!res.ok) {
130
+ const body = await res.text().catch(() => '');
131
+ runtimeLog.warn('ChatClient', `⚠️ listMyChats failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
132
+ throw new Error(`GET /chats/mine ${res.status} ${body?.slice(0, 200)}`);
133
+ }
134
+ const data = await res.json().catch(() => null);
135
+ return Array.isArray(data?.['chats']) ? data['chats'] : [];
132
136
  }
133
137
  export class ChatClient {
134
138
  creds;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",