@ziggs-ai/api-client 0.9.0 → 0.9.2

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 (60) hide show
  1. package/dist/ConnectionManager.d.ts +23 -59
  2. package/dist/ConnectionManager.js +37 -166
  3. package/dist/capabilities/agreements.js +2 -2
  4. package/dist/capabilities/chat.js +13 -3
  5. package/dist/capabilities/connections.js +1 -1
  6. package/dist/capabilities/index.d.ts +1 -1
  7. package/dist/capabilities/index.js +1 -1
  8. package/dist/capabilities/links.d.ts +0 -8
  9. package/dist/capabilities/links.js +3 -25
  10. package/dist/capabilities/payments.js +5 -0
  11. package/dist/capabilities/types.js +3 -0
  12. package/dist/config.d.ts +33 -0
  13. package/dist/config.js +42 -0
  14. package/dist/http/AgentSearchClient.d.ts +0 -1
  15. package/dist/http/AgentSearchClient.js +0 -1
  16. package/dist/http/AgreementClient.d.ts +32 -1
  17. package/dist/http/AgreementClient.js +96 -23
  18. package/dist/http/ArtifactsClient.d.ts +0 -1
  19. package/dist/http/ArtifactsClient.js +0 -1
  20. package/dist/http/ChatClient.d.ts +6 -3
  21. package/dist/http/ChatClient.js +7 -6
  22. package/dist/http/ConnectionsClient.d.ts +0 -1
  23. package/dist/http/ConnectionsClient.js +4 -5
  24. package/dist/http/ContextDiscoveryClient.d.ts +0 -1
  25. package/dist/http/ContextDiscoveryClient.js +2 -2
  26. package/dist/http/ContextGrantsClient.d.ts +0 -1
  27. package/dist/http/ContextGrantsClient.js +0 -1
  28. package/dist/http/ContextReadClient.d.ts +0 -1
  29. package/dist/http/ContextReadClient.js +5 -17
  30. package/dist/http/GrantsClient.d.ts +0 -1
  31. package/dist/http/GrantsClient.js +2 -2
  32. package/dist/http/InboxClient.d.ts +1 -177
  33. package/dist/http/InboxClient.js +0 -40
  34. package/dist/http/MarketplaceClient.d.ts +0 -1
  35. package/dist/http/MarketplaceClient.js +5 -4
  36. package/dist/http/MessagesClient.d.ts +0 -1
  37. package/dist/http/MessagesClient.js +3 -6
  38. package/dist/http/OrgsClient.d.ts +0 -1
  39. package/dist/http/OrgsClient.js +3 -3
  40. package/dist/http/PaymentsClient.d.ts +4 -2
  41. package/dist/http/PaymentsClient.js +27 -7
  42. package/dist/http/TaskClient.d.ts +0 -1
  43. package/dist/http/TaskClient.js +0 -1
  44. package/dist/http/TelemetryClient.d.ts +0 -1
  45. package/dist/http/TelemetryClient.js +0 -1
  46. package/dist/http/agreementFlows.d.ts +13 -5
  47. package/dist/http/agreementFlows.js +18 -24
  48. package/dist/http/index.d.ts +0 -1
  49. package/dist/index.d.ts +5 -3
  50. package/dist/index.js +5 -2
  51. package/dist/shared/apiError.d.ts +22 -1
  52. package/dist/shared/apiError.js +60 -3
  53. package/dist/shared/rateLimit.d.ts +12 -22
  54. package/dist/shared/rateLimit.js +18 -51
  55. package/dist/shared/runtimeLog.d.ts +0 -6
  56. package/dist/shared/runtimeLog.js +10 -7
  57. package/dist/types.d.ts +167 -1
  58. package/dist/types.js +20 -1
  59. package/dist/utils/urlUtils.js +3 -2
  60. package/package.json +1 -2
@@ -1,4 +1,4 @@
1
- import { ApiError } from '../types.js';
1
+ import { ApiError, RateLimitedError } from '../types.js';
2
2
  /**
3
3
  * Read the message out of a Ziggs error response.
4
4
  *
@@ -51,7 +51,64 @@ export function parseErrorMessage(responseBody, defaultMessage) {
51
51
  (preferMessage ? message : (error ?? message)) ??
52
52
  defaultMessage);
53
53
  }
54
- /** Throw the parsed failure as an {@link ApiError}, preserving status and body. */
54
+ /** Machine code from `{ error, code }` when the filter (or thrower) supplied one. */
55
+ export function parseErrorCode(responseBody) {
56
+ if (!responseBody)
57
+ return null;
58
+ try {
59
+ const parsed = JSON.parse(responseBody);
60
+ if (typeof parsed !== 'object' || parsed === null)
61
+ return null;
62
+ const code = parsed['code'];
63
+ return typeof code === 'string' && code ? code : null;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ const MAX_RETRY_AFTER_MS = 120_000;
70
+ /**
71
+ * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
72
+ * one) and is accepted in both forms — delta-seconds or an HTTP date;
73
+ * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
74
+ * bad header cannot park a loop for an hour, floored at a second so a `0` does
75
+ * not reproduce the hot-retry it is meant to stop.
76
+ *
77
+ * Lives next to {@link throwApiError} so every client path (not only the poll
78
+ * surface) can honour the server's number (ZIG-1124).
79
+ */
80
+ export function parseRetryAfterMs(headers) {
81
+ const explicit = headers.get('retry-after');
82
+ if (explicit) {
83
+ const seconds = Number(explicit);
84
+ if (Number.isFinite(seconds))
85
+ return clampRetryMs(seconds * 1000);
86
+ const at = Date.parse(explicit);
87
+ if (!Number.isNaN(at))
88
+ return clampRetryMs(at - Date.now());
89
+ }
90
+ const reset = headers.get('ratelimit-reset');
91
+ if (reset) {
92
+ const seconds = Number(reset);
93
+ if (Number.isFinite(seconds))
94
+ return clampRetryMs(seconds * 1000);
95
+ }
96
+ return null;
97
+ }
98
+ function clampRetryMs(ms) {
99
+ if (!Number.isFinite(ms))
100
+ return 1_000;
101
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
102
+ }
103
+ /**
104
+ * Throw the parsed failure as an {@link ApiError} (or {@link RateLimitedError}
105
+ * for 429), preserving status, body, machine code, and Retry-After.
106
+ */
55
107
  export function throwApiError(response, responseBody, defaultMessage) {
56
- throw new ApiError(parseErrorMessage(responseBody, defaultMessage), response.status, responseBody);
108
+ const message = parseErrorMessage(responseBody, defaultMessage);
109
+ const code = parseErrorCode(responseBody);
110
+ if (response.status === 429) {
111
+ throw new RateLimitedError(message, response.headers ? parseRetryAfterMs(response.headers) : null, responseBody, code);
112
+ }
113
+ throw new ApiError(message, response.status, responseBody, code);
57
114
  }
@@ -1,3 +1,5 @@
1
+ import { ApiError, RateLimitedError } from '../types.js';
2
+ import { parseRetryAfterMs } from './apiError.js';
1
3
  /**
2
4
  * ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
3
5
  * deadline attached.
@@ -11,36 +13,24 @@
11
13
  * The server already says exactly how long to wait — `Retry-After`, or
12
14
  * `RateLimit-Reset` from the standard headers. These helpers carry that number
13
15
  * to whoever is doing the backing off.
16
+ *
17
+ * ZIG-1124 — {@link RateLimitedError} extends {@link ApiError}; non-429 poll
18
+ * failures are ApiError too (same shape as {@link throwApiError}).
14
19
  */
15
- /** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
16
- export declare class RateLimitedError extends Error {
17
- readonly status = 429;
18
- /** How long the server said to wait. Null when it said nothing. */
19
- readonly retryAfterMs: number | null;
20
- constructor(message: string, retryAfterMs: number | null);
21
- }
22
- /** True for the error above — survives structured clones and re-wraps. */
20
+ export { RateLimitedError, parseRetryAfterMs };
21
+ /** True for a 429 that carries a wait field — survives structured clones. */
23
22
  export declare function isRateLimited(err: unknown): err is {
24
23
  retryAfterMs: number | null;
24
+ status: number;
25
25
  };
26
26
  /**
27
- * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
28
- * one) and is accepted in both forms — delta-seconds or an HTTP date;
29
- * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
30
- * bad header cannot park a loop for an hour, floored at a second so a `0` does
31
- * not reproduce the hot-retry it is meant to stop.
32
- */
33
- export declare function parseRetryAfterMs(headers: {
34
- get(name: string): string | null;
35
- }): number | null;
36
- /**
37
- * Build the error for a failed poll-surface response: 429s carry the server's
38
- * wait, everything else stays an ordinary Error so existing handling is
39
- * unchanged.
27
+ * Build the error for a failed poll-surface response. Same shape as
28
+ * {@link throwApiError}: ApiError with status/body/code, or RateLimitedError
29
+ * when the server said 429.
40
30
  */
41
31
  export declare function pollSurfaceError(label: string, res: {
42
32
  status: number;
43
33
  headers: {
44
34
  get(name: string): string | null;
45
35
  };
46
- }, body: string): Error;
36
+ }, body: string): ApiError;
@@ -1,3 +1,5 @@
1
+ import { ApiError, RateLimitedError } from '../types.js';
2
+ import { parseErrorCode, parseErrorMessage, parseRetryAfterMs, } from './apiError.js';
1
3
  /**
2
4
  * ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
3
5
  * deadline attached.
@@ -11,63 +13,28 @@
11
13
  * The server already says exactly how long to wait — `Retry-After`, or
12
14
  * `RateLimit-Reset` from the standard headers. These helpers carry that number
13
15
  * to whoever is doing the backing off.
16
+ *
17
+ * ZIG-1124 — {@link RateLimitedError} extends {@link ApiError}; non-429 poll
18
+ * failures are ApiError too (same shape as {@link throwApiError}).
14
19
  */
15
- /** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
16
- export class RateLimitedError extends Error {
17
- status = 429;
18
- /** How long the server said to wait. Null when it said nothing. */
19
- retryAfterMs;
20
- constructor(message, retryAfterMs) {
21
- super(message);
22
- this.name = 'RateLimitedError';
23
- this.retryAfterMs = retryAfterMs;
24
- }
25
- }
26
- /** True for the error above — survives structured clones and re-wraps. */
20
+ export { RateLimitedError, parseRetryAfterMs };
21
+ /** True for a 429 that carries a wait field — survives structured clones. */
27
22
  export function isRateLimited(err) {
28
23
  return (!!err &&
29
24
  typeof err === 'object' &&
30
- err.status === 429);
31
- }
32
- const MAX_RETRY_AFTER_MS = 120_000;
33
- /**
34
- * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
35
- * one) and is accepted in both forms — delta-seconds or an HTTP date;
36
- * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
37
- * bad header cannot park a loop for an hour, floored at a second so a `0` does
38
- * not reproduce the hot-retry it is meant to stop.
39
- */
40
- export function parseRetryAfterMs(headers) {
41
- const explicit = headers.get('retry-after');
42
- if (explicit) {
43
- const seconds = Number(explicit);
44
- if (Number.isFinite(seconds))
45
- return clampRetryMs(seconds * 1000);
46
- const at = Date.parse(explicit);
47
- if (!Number.isNaN(at))
48
- return clampRetryMs(at - Date.now());
49
- }
50
- const reset = headers.get('ratelimit-reset');
51
- if (reset) {
52
- const seconds = Number(reset);
53
- if (Number.isFinite(seconds))
54
- return clampRetryMs(seconds * 1000);
55
- }
56
- return null;
57
- }
58
- function clampRetryMs(ms) {
59
- if (!Number.isFinite(ms))
60
- return 1_000;
61
- return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
25
+ err.status === 429 &&
26
+ 'retryAfterMs' in err);
62
27
  }
63
28
  /**
64
- * Build the error for a failed poll-surface response: 429s carry the server's
65
- * wait, everything else stays an ordinary Error so existing handling is
66
- * unchanged.
29
+ * Build the error for a failed poll-surface response. Same shape as
30
+ * {@link throwApiError}: ApiError with status/body/code, or RateLimitedError
31
+ * when the server said 429.
67
32
  */
68
33
  export function pollSurfaceError(label, res, body) {
69
- const message = `${label} ${res.status} ${body.slice(0, 200)}`;
70
- if (res.status !== 429)
71
- return new Error(message);
72
- return new RateLimitedError(message, parseRetryAfterMs(res.headers));
34
+ const message = parseErrorMessage(body, `${label} failed: ${res.status}`);
35
+ const code = parseErrorCode(body);
36
+ if (res.status === 429) {
37
+ return new RateLimitedError(message, parseRetryAfterMs(res.headers), body, code);
38
+ }
39
+ return new ApiError(message, res.status, body, code);
73
40
  }
@@ -1,9 +1,3 @@
1
- /**
2
- * Same semantics as `@ziggs-ai/agent-sdk` `shared/runtimeLog.ts` (duplicated
3
- * here so this package stays dependency-free).
4
- *
5
- * @see agent-sdk/src/shared/runtimeLog.ts
6
- */
7
1
  export type RuntimeLogLevel = 'debug' | 'info' | 'warn' | 'error';
8
2
  export declare function resetRuntimeLogLevelCache(): void;
9
3
  export declare const runtimeLog: {
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * @see agent-sdk/src/shared/runtimeLog.ts
6
6
  */
7
+ import { apiClientConfig, apiClientConfigVersion } from '../config.js';
7
8
  const SEVERITY = {
8
9
  debug: 0,
9
10
  info: 1,
@@ -11,12 +12,7 @@ const SEVERITY = {
11
12
  error: 3,
12
13
  };
13
14
  function parseThreshold() {
14
- if (process.env.DEBUG_AGENTPLUS === '1' || process.env.AGENTPLUS_DEBUG === '1') {
15
- return SEVERITY.debug;
16
- }
17
- const raw = (process.env.LOG_LEVEL ||
18
- process.env.AGENTPLUS_LOG_LEVEL ||
19
- 'info').toLowerCase();
15
+ const raw = (apiClientConfig().logLevel || 'info').toLowerCase();
20
16
  if (raw === 'silent' || raw === 'none')
21
17
  return SEVERITY.error;
22
18
  if (raw === 'debug' || raw === 'trace')
@@ -28,13 +24,20 @@ function parseThreshold() {
28
24
  return SEVERITY.info;
29
25
  }
30
26
  let cachedThreshold = null;
27
+ let cachedVersion = -1;
31
28
  function threshold() {
32
- if (cachedThreshold === null)
29
+ // Recompute when the host reconfigures, so a `configureApiClient` call after
30
+ // the first log line still takes effect.
31
+ const v = apiClientConfigVersion();
32
+ if (cachedThreshold === null || cachedVersion !== v) {
33
33
  cachedThreshold = parseThreshold();
34
+ cachedVersion = v;
35
+ }
34
36
  return cachedThreshold;
35
37
  }
36
38
  export function resetRuntimeLogLevelCache() {
37
39
  cachedThreshold = null;
40
+ cachedVersion = -1;
38
41
  }
39
42
  function shouldEmit(level) {
40
43
  return SEVERITY[level] >= threshold();
package/dist/types.d.ts CHANGED
@@ -1,7 +1,21 @@
1
+ /**
2
+ * One failure shape for every HTTP client path (ZIG-1124).
3
+ *
4
+ * `status` and optional machine `code` come from the response; `body` is the
5
+ * raw text so callers can re-parse without scraping concatenated messages.
6
+ * A 429 is {@link RateLimitedError}, which adds the server's wait.
7
+ */
1
8
  export declare class ApiError extends Error {
2
9
  readonly status: number;
3
10
  readonly body?: string | undefined;
4
- constructor(message: string, status: number, body?: string | undefined);
11
+ readonly code?: string | null | undefined;
12
+ constructor(message: string, status: number, body?: string | undefined, code?: string | null | undefined);
13
+ }
14
+ /** HTTP 429 — same fields as {@link ApiError}, plus the server's Retry-After. */
15
+ export declare class RateLimitedError extends ApiError {
16
+ /** How long the server said to wait. Null when it said nothing. */
17
+ readonly retryAfterMs: number | null;
18
+ constructor(message: string, retryAfterMs: number | null, body?: string, code?: string | null);
5
19
  }
6
20
  export interface Creds {
7
21
  operatorKey: string;
@@ -255,3 +269,155 @@ export interface MessageMetadata {
255
269
  sentTimestamp?: string;
256
270
  }
257
271
  export type MessageHandler = (text: string, metadata: MessageMetadata) => Promise<void>;
272
+ /**
273
+ * What a delivery can be about. A closed union, not a comment: a consumer that
274
+ * dispatches on `kind` (the MCP read plan) is only safe if the compiler can tell
275
+ * it a case is missing. The task-only deliverable that was acked unread got
276
+ * through precisely because this was `string`.
277
+ *
278
+ * A type and not a value list, unlike the backend's `RESOURCE_KINDS` — nothing
279
+ * on this side validates a delivery kind at runtime (the server does that on the
280
+ * way in), and exhaustiveness checking is purely type-level.
281
+ */
282
+ export type InboxDeliveryKind = 'message' | 'artifact' | 'task-state' | 'agreement' | 'quest';
283
+ /**
284
+ * One thing addressed to this agent. A reference, never content — following it
285
+ * (a chat read, a task read) is where this agent's grants are enforced.
286
+ */
287
+ export interface InboxDeliveryRef {
288
+ kind: InboxDeliveryKind;
289
+ resourceId: string;
290
+ chatId: string | null;
291
+ agreementId: string | null;
292
+ taskId: string | null;
293
+ /** Who wrote it. Never this agent — you are not woken by your own writes. */
294
+ actorId: string | null;
295
+ ts: string;
296
+ /**
297
+ * ZIG-703 — lifecycle hint when the server includes one (e.g.
298
+ * `connection_request_fulfilled`). Absent on ordinary doorbells / older servers.
299
+ */
300
+ reason?: string | null;
301
+ /** ZIG-703 — MCP connection after a fulfilled first-hop request. */
302
+ connectionId?: string | null;
303
+ /** ZIG-703 — grant minted for this agent on fulfill. */
304
+ grantId?: string | null;
305
+ }
306
+ /** Message/artifact deliveries folded by chat, so you can open chats directly. */
307
+ export interface InboxChatNews {
308
+ chatId: string;
309
+ count: number;
310
+ latestAt: string;
311
+ }
312
+ export interface InboxProposalRef {
313
+ agreementId: string;
314
+ title: string;
315
+ proposedAt: string | null;
316
+ /**
317
+ * ZIG-1087 — party ids still owing a decision, and the named responder slot.
318
+ * The inbox lists proposals awaiting the agent OR its human, and only the
319
+ * agent's own slot is one it can submit; these say which is which.
320
+ *
321
+ * Optional because a backend deployed before ZIG-1087 omits them, and this
322
+ * client is installed independently of the server it talks to. Absent reads
323
+ * as "no slot of mine", which routes the decision to the human — the safe
324
+ * direction: it withholds a call, it never invents authority.
325
+ */
326
+ pendingApprovalPartyIds?: string[];
327
+ proposedTo?: string | null;
328
+ }
329
+ export interface InboxConnectionRequestRef {
330
+ requestId: string;
331
+ /**
332
+ * Non-addressable persona reference for the requester (`psn_*`).
333
+ * Never use as an account id for lookup / wake / pay (ZIG-1137).
334
+ */
335
+ requesterRef: string;
336
+ /** ZIG-1039 — human-readable name for consent cards. */
337
+ requesterDisplayName?: string | null;
338
+ /** ZIG-1039 — org label for consent cards. */
339
+ requesterOrgName?: string | null;
340
+ message: string | null;
341
+ requestedAt: string | null;
342
+ /** ZIG-1087 — see InboxProposalRef; a link request uses the same gate. */
343
+ pendingApprovalPartyIds?: string[];
344
+ proposedTo?: string | null;
345
+ }
346
+ /** Pull-only MCP: prompt the human when proposals need a decision (ZIG-482 / ZIG-481). */
347
+ export interface InboxHumanAttention {
348
+ required: true;
349
+ reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | 'multiple';
350
+ proposalCount: number;
351
+ truncatedProposals: number;
352
+ connectionRequestCount: number;
353
+ truncatedConnectionRequests: number;
354
+ promptUser: string;
355
+ }
356
+ /**
357
+ * An open task assigned to this agent (ZIG-973). References only — read the
358
+ * task for its description/plan/inputs.
359
+ *
360
+ * Tasks ride their own channel because assignment IS their delivery: before
361
+ * this, a task wake was a synthetic chat row, so work under a chat-less
362
+ * agreement (or self-assigned) reached nobody.
363
+ */
364
+ export interface InboxTaskRef {
365
+ taskId: string;
366
+ agreementId: string | null;
367
+ title: string;
368
+ state: string;
369
+ updatedAt: string | null;
370
+ }
371
+ /**
372
+ * A marketplace quest doorbell (ZIG-1185). Own channel so the host can
373
+ * exact-match triage with zero LLM tokens before any wake.
374
+ */
375
+ export interface InboxQuestRef {
376
+ agreementId: string;
377
+ /** Exact-match string from the publisher — compare to the agent's tags. */
378
+ match: string;
379
+ title: string;
380
+ ts: string;
381
+ }
382
+ export interface InboxEnvelope {
383
+ asOf: string;
384
+ /**
385
+ * Unacked deliveries addressed to this agent, newest first. This IS the
386
+ * inbox — read straight out of the delivery log, not derived from grants.
387
+ */
388
+ deliveries: InboxDeliveryRef[];
389
+ /** True when there was more than one envelope's worth; the rest stay unacked. */
390
+ deliveriesCapped: boolean;
391
+ /** The chat-bearing deliveries above, folded by chat. */
392
+ chats: InboxChatNews[];
393
+ /**
394
+ * Pass to `ack()` after acting. Null when there is nothing to ack. Ack after
395
+ * acting, not after reading: a crash in between redelivers.
396
+ */
397
+ ackTo: string | null;
398
+ /** Open tasks assigned to this agent — the work channel (ZIG-973). */
399
+ tasksAwaitingMe: InboxTaskRef[];
400
+ truncatedTasks: number;
401
+ /** Unacked quest deliveries (ZIG-1185) — triaged before any LLM wake. */
402
+ questsAwaitingMe?: InboxQuestRef[];
403
+ truncatedQuests?: number;
404
+ proposalsAwaitingMe: InboxProposalRef[];
405
+ truncatedProposals: number;
406
+ connectionRequestsAwaitingMe: InboxConnectionRequestRef[];
407
+ truncatedConnectionRequests: number;
408
+ humanAttention?: InboxHumanAttention;
409
+ }
410
+ export interface InboxAckResult {
411
+ /** Where the watermark now sits. Monotonic — a rewind is a no-op, not an error. */
412
+ ackedUpTo: string | null;
413
+ }
414
+ /**
415
+ * Long-poll option shared by the inbox reads. The server holds the request up
416
+ * to this many seconds (server-clamped, ~25s ceiling) and returns as soon as
417
+ * anything actionable exists. Omit for an immediate snapshot — the response
418
+ * shape is identical either way, so `wait` only changes how long an EMPTY
419
+ * answer is withheld.
420
+ */
421
+ export interface InboxReadOptions {
422
+ waitSeconds?: number;
423
+ }
package/dist/types.js CHANGED
@@ -1,13 +1,32 @@
1
+ /**
2
+ * One failure shape for every HTTP client path (ZIG-1124).
3
+ *
4
+ * `status` and optional machine `code` come from the response; `body` is the
5
+ * raw text so callers can re-parse without scraping concatenated messages.
6
+ * A 429 is {@link RateLimitedError}, which adds the server's wait.
7
+ */
1
8
  export class ApiError extends Error {
2
9
  status;
3
10
  body;
4
- constructor(message, status, body) {
11
+ code;
12
+ constructor(message, status, body, code) {
5
13
  super(message);
6
14
  this.status = status;
7
15
  this.body = body;
16
+ this.code = code;
8
17
  this.name = 'ApiError';
9
18
  }
10
19
  }
20
+ /** HTTP 429 — same fields as {@link ApiError}, plus the server's Retry-After. */
21
+ export class RateLimitedError extends ApiError {
22
+ /** How long the server said to wait. Null when it said nothing. */
23
+ retryAfterMs;
24
+ constructor(message, retryAfterMs, body, code) {
25
+ super(message, 429, body, code);
26
+ this.name = 'RateLimitedError';
27
+ this.retryAfterMs = retryAfterMs;
28
+ }
29
+ }
11
30
  /** ⚠️ SYNC: backend src/agreements/agreements.constants.ts AGREEMENT_ENGAGEMENT_KIND */
12
31
  export const AGREEMENT_ENGAGEMENT_KIND = {
13
32
  HIRE: 'hire',
@@ -1,8 +1,9 @@
1
+ import { apiClientConfig } from '../config.js';
1
2
  export function getBackendUrl() {
2
- const url = process.env.HTTP_URL || 'https://api.ziggsai.com';
3
+ const url = apiClientConfig().httpUrl || 'https://api.ziggsai.com';
3
4
  return url.startsWith('http') ? url : `https://${url}`;
4
5
  }
5
6
  export function getWebSocketUrl() {
6
- const wsUrl = process.env.WS_URL || 'wss://api.ziggsai.com';
7
+ const wsUrl = apiClientConfig().wsUrl || 'wss://api.ziggsai.com';
7
8
  return wsUrl.startsWith('ws') ? wsUrl : `wss://${wsUrl}`;
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,6 @@
32
32
  "test:watch": "node --import tsx/esm --test --watch test/*.test.ts"
33
33
  },
34
34
  "dependencies": {
35
- "dotenv": "^17.2.3",
36
35
  "socket.io-client": "^4.7.0"
37
36
  },
38
37
  "keywords": [