@ziggs-ai/api-client 0.9.0 → 0.9.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
@@ -2,12 +2,12 @@ export * from './http/index.js';
2
2
  export * from './capabilities/index.js';
3
3
  export * from './relay/provisionRelayWorkers.js';
4
4
  export { ConnectionManager } from './ConnectionManager.js';
5
- export type { StartAgentOptions } from './ConnectionManager.js';
6
5
  export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, isPersonaRef, isRoomPresentationRef, isOpaquePresentationRef, AGREEMENT_ENGAGEMENT_KIND, isValidContentType, } from './types.js';
7
6
  export type { PrincipalPresentation } from './types.js';
8
7
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
9
8
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
10
9
  export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
11
- export { parseErrorMessage, throwApiError } from './shared/apiError.js';
12
- export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, ApiError, } from './types.js';
10
+ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
11
+ export { ApiError } from './types.js';
12
+ export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, } from './types.js';
13
13
  export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, } from './http/AgreementClient.js';
package/dist/index.js CHANGED
@@ -5,9 +5,10 @@ export { ConnectionManager } from './ConnectionManager.js';
5
5
  export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, isPersonaRef, isRoomPresentationRef, isOpaquePresentationRef, AGREEMENT_ENGAGEMENT_KIND, isValidContentType, } from './types.js';
6
6
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
7
7
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
8
- // ZIG-1019: retry loops need the server's own wait, not a guess.
8
+ // ZIG-1019 / ZIG-1124: one ApiError shape; 429s carry the server's wait.
9
9
  export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
10
10
  // One reader for the one error shape the API answers in — exported so nothing has
11
11
  // to re-implement the field precedence (it was copy-pasted into six clients, and
12
12
  // half of them had drifted).
13
- export { parseErrorMessage, throwApiError } from './shared/apiError.js';
13
+ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
14
+ export { ApiError } from './types.js';
@@ -16,7 +16,28 @@
16
16
  * so the same failure read differently depending on which client you called.
17
17
  */
18
18
  export declare function parseErrorMessage(responseBody: string, defaultMessage: string): string;
19
- /** Throw the parsed failure as an {@link ApiError}, preserving status and body. */
19
+ /** Machine code from `{ error, code }` when the filter (or thrower) supplied one. */
20
+ export declare function parseErrorCode(responseBody: string): string | null;
21
+ /**
22
+ * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
23
+ * one) and is accepted in both forms — delta-seconds or an HTTP date;
24
+ * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
25
+ * bad header cannot park a loop for an hour, floored at a second so a `0` does
26
+ * not reproduce the hot-retry it is meant to stop.
27
+ *
28
+ * Lives next to {@link throwApiError} so every client path (not only the poll
29
+ * surface) can honour the server's number (ZIG-1124).
30
+ */
31
+ export declare function parseRetryAfterMs(headers: {
32
+ get(name: string): string | null;
33
+ }): number | null;
34
+ /**
35
+ * Throw the parsed failure as an {@link ApiError} (or {@link RateLimitedError}
36
+ * for 429), preserving status, body, machine code, and Retry-After.
37
+ */
20
38
  export declare function throwApiError(response: {
21
39
  status: number;
40
+ headers?: {
41
+ get(name: string): string | null;
42
+ };
22
43
  }, responseBody: string, defaultMessage: string): never;
@@ -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
  }
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;
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',
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.1",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",