@pellux/goodvibes-errors 0.18.3 → 0.30.0

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/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # @pellux/goodvibes-errors
2
2
 
3
- Structured GoodVibes SDK error types.
3
+ Internal workspace package backing `@pellux/goodvibes-sdk/errors`.
4
4
 
5
- Install:
5
+ Consumers should install `@pellux/goodvibes-sdk` and import this surface from the umbrella package.
6
6
 
7
- ```bash
8
- npm install @pellux/goodvibes-errors
7
+ Consumer import:
8
+
9
+ ```ts
10
+ import { HttpStatusError } from '@pellux/goodvibes-sdk/errors';
9
11
  ```
10
12
 
11
13
  Use this package when you need to branch on:
@@ -19,7 +21,7 @@ Use this package when you need to branch on:
19
21
  Example:
20
22
 
21
23
  ```ts
22
- import { HttpStatusError } from '@pellux/goodvibes-errors';
24
+ import { HttpStatusError } from '@pellux/goodvibes-sdk/errors';
23
25
 
24
26
  try {
25
27
  // integration code
@@ -1,4 +1,21 @@
1
1
  export type DaemonErrorCategory = 'authentication' | 'authorization' | 'billing' | 'rate_limit' | 'timeout' | 'network' | 'bad_request' | 'not_found' | 'permission' | 'tool' | 'config' | 'protocol' | 'service' | 'internal' | 'unknown';
2
+ export declare const DaemonErrorCategory: {
3
+ readonly AUTHENTICATION: "authentication";
4
+ readonly AUTHORIZATION: "authorization";
5
+ readonly BILLING: "billing";
6
+ readonly RATE_LIMIT: "rate_limit";
7
+ readonly TIMEOUT: "timeout";
8
+ readonly NETWORK: "network";
9
+ readonly BAD_REQUEST: "bad_request";
10
+ readonly NOT_FOUND: "not_found";
11
+ readonly PERMISSION: "permission";
12
+ readonly TOOL: "tool";
13
+ readonly CONFIG: "config";
14
+ readonly PROTOCOL: "protocol";
15
+ readonly SERVICE: "service";
16
+ readonly INTERNAL: "internal";
17
+ readonly UNKNOWN: "unknown";
18
+ };
2
19
  export type DaemonErrorSource = 'provider' | 'tool' | 'transport' | 'config' | 'permission' | 'runtime' | 'render' | 'acp' | 'unknown';
3
20
  export interface StructuredDaemonErrorBody {
4
21
  readonly error: string;
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-error-contract.d.ts","sourceRoot":"","sources":["../src/daemon-error-contract.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,mBAAmB,GAC3B,gBAAgB,GAChB,eAAe,GACf,SAAS,GACT,YAAY,GACZ,SAAS,GACT,SAAS,GACT,aAAa,GACb,WAAW,GACX,YAAY,GACZ,MAAM,GACN,QAAQ,GACR,UAAU,GACV,SAAS,GACT,UAAU,GACV,SAAS,CAAC;AAEd,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,MAAM,GACN,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,QAAQ,GACR,KAAK,GACL,SAAS,CAAC;AAEd,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC"}
1
+ {"version":3,"file":"daemon-error-contract.d.ts","sourceRoot":"","sources":["../src/daemon-error-contract.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAC3B,gBAAgB,GAChB,eAAe,GACf,SAAS,GACT,YAAY,GACZ,SAAS,GACT,SAAS,GACT,aAAa,GACb,WAAW,GACX,YAAY,GACZ,MAAM,GACN,QAAQ,GACR,UAAU,GACV,SAAS,GACT,UAAU,GACV,SAAS,CAAC;AAEd,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;CAgBwB,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,MAAM,GACN,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,QAAQ,GACR,KAAK,GACL,SAAS,CAAC;AAEd,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC"}
@@ -1 +1,17 @@
1
- export {};
1
+ export const DaemonErrorCategory = {
2
+ AUTHENTICATION: 'authentication',
3
+ AUTHORIZATION: 'authorization',
4
+ BILLING: 'billing',
5
+ RATE_LIMIT: 'rate_limit',
6
+ TIMEOUT: 'timeout',
7
+ NETWORK: 'network',
8
+ BAD_REQUEST: 'bad_request',
9
+ NOT_FOUND: 'not_found',
10
+ PERMISSION: 'permission',
11
+ TOOL: 'tool',
12
+ CONFIG: 'config',
13
+ PROTOCOL: 'protocol',
14
+ SERVICE: 'service',
15
+ INTERNAL: 'internal',
16
+ UNKNOWN: 'unknown',
17
+ };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,22 @@
1
1
  import type { DaemonErrorCategory, DaemonErrorSource, StructuredDaemonErrorBody } from './daemon-error-contract.js';
2
- export type { DaemonErrorCategory, DaemonErrorSource, StructuredDaemonErrorBody, } from './daemon-error-contract.js';
2
+ export type { DaemonErrorSource, StructuredDaemonErrorBody, } from './daemon-error-contract.js';
3
+ export { DaemonErrorCategory } from './daemon-error-contract.js';
3
4
  export type ErrorCategory = DaemonErrorCategory | 'contract';
4
5
  export type ErrorSource = DaemonErrorSource | 'contract';
6
+ /**
7
+ * Tagged union discriminant for all SDK errors. Use this for exhaustive
8
+ * switch/if-else handling instead of `instanceof` chains.
9
+ *
10
+ * @example
11
+ * if (error instanceof GoodVibesSdkError) {
12
+ * if (error.kind === 'rate-limit') {
13
+ * await delay(error.retryAfterMs ?? 1000);
14
+ * } else if (error.kind === 'auth') {
15
+ * // refresh credentials
16
+ * }
17
+ * }
18
+ */
19
+ export type SDKErrorKind = 'auth' | 'config' | 'contract' | 'network' | 'not-found' | 'protocol' | 'rate-limit' | 'service' | 'internal' | 'tool' | 'validation' | 'unknown';
5
20
  export interface GoodVibesSdkErrorOptions {
6
21
  readonly code?: string;
7
22
  readonly category?: ErrorCategory;
@@ -19,9 +34,34 @@ export interface GoodVibesSdkErrorOptions {
19
34
  readonly providerCode?: string;
20
35
  readonly providerType?: string;
21
36
  readonly retryAfterMs?: number;
37
+ readonly cause?: unknown;
22
38
  }
23
39
  export declare const RETRYABLE_STATUS_CODES: readonly number[];
40
+ /**
41
+ * Base error class for all errors thrown by the GoodVibes SDK.
42
+ *
43
+ * Every error carries a structured `category` and `source` that allow
44
+ * callers to handle specific failure modes without string-matching messages.
45
+ *
46
+ * ### Narrowing pattern
47
+ * ```ts
48
+ * import { GoodVibesSdkError, HttpStatusError, ConfigurationError } from '@pellux/goodvibes-sdk';
49
+ *
50
+ * try {
51
+ * await sdk.operator.agents.list();
52
+ * } catch (err) {
53
+ * if (err instanceof HttpStatusError && err.category === 'rate_limit') {
54
+ * // Back off and retry after err.retryAfterMs
55
+ * } else if (err instanceof ConfigurationError) {
56
+ * // Invalid SDK setup — not recoverable
57
+ * } else if (err instanceof GoodVibesSdkError) {
58
+ * console.error(err.category, err.hint);
59
+ * }
60
+ * }
61
+ * ```
62
+ */
24
63
  export declare class GoodVibesSdkError extends Error {
64
+ readonly kind: SDKErrorKind;
25
65
  readonly code?: string;
26
66
  readonly category: ErrorCategory;
27
67
  readonly source: ErrorSource;
@@ -38,17 +78,86 @@ export declare class GoodVibesSdkError extends Error {
38
78
  readonly providerCode?: string;
39
79
  readonly providerType?: string;
40
80
  readonly retryAfterMs?: number;
81
+ readonly cause?: unknown;
82
+ static [Symbol.hasInstance](value: unknown): boolean;
41
83
  constructor(message: string, options?: GoodVibesSdkErrorOptions);
84
+ toJSON(): Record<string, unknown>;
42
85
  }
86
+ /**
87
+ * Thrown when the SDK is misconfigured (e.g. missing `baseUrl`, no fetch
88
+ * implementation available, or calling a mutation on a read-only auth resolver).
89
+ *
90
+ * Always non-recoverable (`recoverable: false`).
91
+ * Category: `'config'`. Kind: `'config'`.
92
+ *
93
+ * @example
94
+ * import { ConfigurationError } from '@pellux/goodvibes-sdk';
95
+ *
96
+ * try {
97
+ * await sdk.auth.setToken('x');
98
+ * } catch (err) {
99
+ * if (err instanceof ConfigurationError) {
100
+ * // SDK was constructed with getAuthToken — token mutation not supported
101
+ * }
102
+ * }
103
+ */
43
104
  export declare class ConfigurationError extends GoodVibesSdkError {
105
+ static [Symbol.hasInstance](value: unknown): boolean;
44
106
  constructor(message: string, options?: GoodVibesSdkErrorOptions);
45
107
  }
108
+ /**
109
+ * Thrown when a response from the daemon violates the expected contract
110
+ * (unexpected shape, missing required fields, etc.).
111
+ *
112
+ * Always non-recoverable (`recoverable: false`).
113
+ * Category: `'contract'`. Kind: `'contract'`.
114
+ *
115
+ * @example
116
+ * import { ContractError } from '@pellux/goodvibes-sdk';
117
+ *
118
+ * try {
119
+ * const result = await sdk.operator.agents.get({ id: agentId });
120
+ * } catch (err) {
121
+ * if (err instanceof ContractError) {
122
+ * // Daemon returned an unexpected shape — SDK version mismatch?
123
+ * console.error('Contract violation:', err.message);
124
+ * }
125
+ * }
126
+ */
46
127
  export declare class ContractError extends GoodVibesSdkError {
128
+ static [Symbol.hasInstance](value: unknown): boolean;
47
129
  constructor(message: string, options?: GoodVibesSdkErrorOptions);
48
130
  }
131
+ /**
132
+ * Thrown when the daemon returns a non-2xx HTTP status code.
133
+ *
134
+ * The `category` field is inferred from the status code:
135
+ * - `401` → `'authentication'` `402` → `'billing'` `403` → `'authorization'`
136
+ * - `404` → `'not_found'` `408` → `'timeout'` `429` → `'rate_limit'`
137
+ * - `5xx` → `'service'`
138
+ *
139
+ * Use `recoverable` to decide whether to retry, and `retryAfterMs` for
140
+ * the backoff hint on rate-limit responses.
141
+ *
142
+ * @example
143
+ * import { HttpStatusError } from '@pellux/goodvibes-sdk';
144
+ *
145
+ * try {
146
+ * await sdk.operator.agents.list();
147
+ * } catch (err) {
148
+ * if (err instanceof HttpStatusError) {
149
+ * if (err.category === 'rate_limit') {
150
+ * await delay(err.retryAfterMs ?? 1000);
151
+ * } else if (!err.recoverable) {
152
+ * throw err; // Surface non-retryable errors immediately
153
+ * }
154
+ * }
155
+ * }
156
+ */
49
157
  export declare class HttpStatusError extends GoodVibesSdkError {
158
+ static [Symbol.hasInstance](value: unknown): boolean;
50
159
  constructor(message: string, options?: GoodVibesSdkErrorOptions);
51
160
  }
52
161
  export declare function isStructuredDaemonErrorBody(value: unknown): value is StructuredDaemonErrorBody;
53
- export declare function createHttpStatusError(status: number, url: string, method: string, body: unknown): HttpStatusError;
162
+ export declare function createHttpStatusError(status: number, url: string, method: string, body: unknown, fallbackHint?: string): HttpStatusError;
54
163
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,37 @@
1
+ export { DaemonErrorCategory } from './daemon-error-contract.js';
2
+ function inferKind(category) {
3
+ switch (category) {
4
+ case 'authentication':
5
+ case 'authorization':
6
+ case 'billing':
7
+ case 'permission':
8
+ return 'auth';
9
+ case 'config':
10
+ return 'config';
11
+ case 'contract':
12
+ return 'contract';
13
+ case 'network':
14
+ case 'timeout':
15
+ return 'network';
16
+ case 'not_found':
17
+ return 'not-found';
18
+ case 'rate_limit':
19
+ return 'rate-limit';
20
+ case 'protocol':
21
+ return 'protocol';
22
+ case 'internal':
23
+ return 'internal';
24
+ case 'service':
25
+ return 'service';
26
+ case 'bad_request':
27
+ return 'validation';
28
+ case 'tool':
29
+ return 'tool';
30
+ case 'unknown':
31
+ default:
32
+ return 'unknown';
33
+ }
34
+ }
1
35
  export const RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504];
2
36
  function inferCategory(status) {
3
37
  if (status === 400)
@@ -18,7 +52,70 @@ function inferCategory(status) {
18
52
  return 'service';
19
53
  return 'unknown';
20
54
  }
55
+ const ERROR_CATEGORIES = new Set([
56
+ 'authentication',
57
+ 'authorization',
58
+ 'bad_request',
59
+ 'billing',
60
+ 'config',
61
+ 'contract',
62
+ 'internal',
63
+ 'network',
64
+ 'not_found',
65
+ 'permission',
66
+ 'protocol',
67
+ 'rate_limit',
68
+ 'service',
69
+ 'timeout',
70
+ 'tool',
71
+ 'unknown',
72
+ ]);
73
+ const GOODVIBES_SDK_ERROR_BRAND = Symbol.for('pellux.goodvibes.sdk.error');
74
+ function readErrorCategory(value) {
75
+ return typeof value === 'string' && ERROR_CATEGORIES.has(value)
76
+ ? value
77
+ : undefined;
78
+ }
79
+ function inferCategoryFromCause(cause, seen = new Set()) {
80
+ if (!cause || typeof cause !== 'object')
81
+ return undefined;
82
+ const objectCause = cause;
83
+ if (seen.has(objectCause))
84
+ return undefined;
85
+ seen.add(objectCause);
86
+ const record = cause;
87
+ const category = readErrorCategory(record.category);
88
+ if (category && category !== 'unknown')
89
+ return category;
90
+ return inferCategoryFromCause(record.cause, seen)
91
+ ?? inferCategoryFromCause(record.originalError, seen)
92
+ ?? inferCategoryFromCause(record.error, seen);
93
+ }
94
+ /**
95
+ * Base error class for all errors thrown by the GoodVibes SDK.
96
+ *
97
+ * Every error carries a structured `category` and `source` that allow
98
+ * callers to handle specific failure modes without string-matching messages.
99
+ *
100
+ * ### Narrowing pattern
101
+ * ```ts
102
+ * import { GoodVibesSdkError, HttpStatusError, ConfigurationError } from '@pellux/goodvibes-sdk';
103
+ *
104
+ * try {
105
+ * await sdk.operator.agents.list();
106
+ * } catch (err) {
107
+ * if (err instanceof HttpStatusError && err.category === 'rate_limit') {
108
+ * // Back off and retry after err.retryAfterMs
109
+ * } else if (err instanceof ConfigurationError) {
110
+ * // Invalid SDK setup — not recoverable
111
+ * } else if (err instanceof GoodVibesSdkError) {
112
+ * console.error(err.category, err.hint);
113
+ * }
114
+ * }
115
+ * ```
116
+ */
21
117
  export class GoodVibesSdkError extends Error {
118
+ kind;
22
119
  code;
23
120
  category;
24
121
  source;
@@ -35,11 +132,37 @@ export class GoodVibesSdkError extends Error {
35
132
  providerCode;
36
133
  providerType;
37
134
  retryAfterMs;
135
+ cause;
136
+ static [Symbol.hasInstance](value) {
137
+ if (this !== GoodVibesSdkError) {
138
+ return typeof value === 'object'
139
+ && value !== null
140
+ && this.prototype.isPrototypeOf(value);
141
+ }
142
+ if (typeof value !== 'object' || value === null)
143
+ return false;
144
+ const record = value;
145
+ return record[GOODVIBES_SDK_ERROR_BRAND] === true
146
+ || (value instanceof Error
147
+ && typeof record.kind === 'string'
148
+ && typeof record.category === 'string'
149
+ && typeof record.source === 'string'
150
+ && typeof record.recoverable === 'boolean');
151
+ }
38
152
  constructor(message, options = {}) {
39
- super(message);
153
+ const category = options.category
154
+ ?? inferCategoryFromCause(options.cause)
155
+ ?? inferCategory(options.status);
156
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
40
157
  this.name = this.constructor.name;
158
+ Object.defineProperty(this, GOODVIBES_SDK_ERROR_BRAND, {
159
+ value: true,
160
+ enumerable: false,
161
+ configurable: false,
162
+ });
41
163
  this.code = options.code;
42
- this.category = options.category ?? inferCategory(options.status);
164
+ this.category = category;
165
+ this.kind = inferKind(this.category);
43
166
  this.source = options.source ?? 'unknown';
44
167
  this.recoverable = options.recoverable ?? (options.status !== undefined && RETRYABLE_STATUS_CODES.includes(options.status));
45
168
  this.status = options.status;
@@ -54,9 +177,76 @@ export class GoodVibesSdkError extends Error {
54
177
  this.providerCode = options.providerCode;
55
178
  this.providerType = options.providerType;
56
179
  this.retryAfterMs = options.retryAfterMs;
180
+ this.cause = options.cause;
181
+ }
182
+ toJSON() {
183
+ return omitUndefined({
184
+ name: this.name,
185
+ message: this.message,
186
+ kind: this.kind,
187
+ code: this.code,
188
+ category: this.category,
189
+ source: this.source,
190
+ recoverable: this.recoverable,
191
+ status: this.status,
192
+ url: this.url,
193
+ method: this.method,
194
+ body: this.body,
195
+ hint: this.hint,
196
+ provider: this.provider,
197
+ operation: this.operation,
198
+ phase: this.phase,
199
+ requestId: this.requestId,
200
+ providerCode: this.providerCode,
201
+ providerType: this.providerType,
202
+ retryAfterMs: this.retryAfterMs,
203
+ cause: serializeCause(this.cause),
204
+ });
57
205
  }
58
206
  }
207
+ function serializeCause(cause) {
208
+ if (cause === undefined)
209
+ return undefined;
210
+ if (cause instanceof Error) {
211
+ return omitUndefined({
212
+ name: cause.name,
213
+ message: cause.message,
214
+ });
215
+ }
216
+ return cause;
217
+ }
218
+ function omitUndefined(record) {
219
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
220
+ }
221
+ /**
222
+ * Thrown when the SDK is misconfigured (e.g. missing `baseUrl`, no fetch
223
+ * implementation available, or calling a mutation on a read-only auth resolver).
224
+ *
225
+ * Always non-recoverable (`recoverable: false`).
226
+ * Category: `'config'`. Kind: `'config'`.
227
+ *
228
+ * @example
229
+ * import { ConfigurationError } from '@pellux/goodvibes-sdk';
230
+ *
231
+ * try {
232
+ * await sdk.auth.setToken('x');
233
+ * } catch (err) {
234
+ * if (err instanceof ConfigurationError) {
235
+ * // SDK was constructed with getAuthToken — token mutation not supported
236
+ * }
237
+ * }
238
+ */
59
239
  export class ConfigurationError extends GoodVibesSdkError {
240
+ static [Symbol.hasInstance](value) {
241
+ if (this !== ConfigurationError) {
242
+ return typeof value === 'object'
243
+ && value !== null
244
+ && this.prototype.isPrototypeOf(value);
245
+ }
246
+ return typeof value === 'object'
247
+ && value !== null
248
+ && value.code === 'SDK_CONFIGURATION_ERROR';
249
+ }
60
250
  constructor(message, options = {}) {
61
251
  super(message, {
62
252
  ...options,
@@ -67,7 +257,36 @@ export class ConfigurationError extends GoodVibesSdkError {
67
257
  });
68
258
  }
69
259
  }
260
+ /**
261
+ * Thrown when a response from the daemon violates the expected contract
262
+ * (unexpected shape, missing required fields, etc.).
263
+ *
264
+ * Always non-recoverable (`recoverable: false`).
265
+ * Category: `'contract'`. Kind: `'contract'`.
266
+ *
267
+ * @example
268
+ * import { ContractError } from '@pellux/goodvibes-sdk';
269
+ *
270
+ * try {
271
+ * const result = await sdk.operator.agents.get({ id: agentId });
272
+ * } catch (err) {
273
+ * if (err instanceof ContractError) {
274
+ * // Daemon returned an unexpected shape — SDK version mismatch?
275
+ * console.error('Contract violation:', err.message);
276
+ * }
277
+ * }
278
+ */
70
279
  export class ContractError extends GoodVibesSdkError {
280
+ static [Symbol.hasInstance](value) {
281
+ if (this !== ContractError) {
282
+ return typeof value === 'object'
283
+ && value !== null
284
+ && this.prototype.isPrototypeOf(value);
285
+ }
286
+ return typeof value === 'object'
287
+ && value !== null
288
+ && value.code === 'SDK_CONTRACT_ERROR';
289
+ }
71
290
  constructor(message, options = {}) {
72
291
  super(message, {
73
292
  ...options,
@@ -78,7 +297,44 @@ export class ContractError extends GoodVibesSdkError {
78
297
  });
79
298
  }
80
299
  }
300
+ /**
301
+ * Thrown when the daemon returns a non-2xx HTTP status code.
302
+ *
303
+ * The `category` field is inferred from the status code:
304
+ * - `401` → `'authentication'` `402` → `'billing'` `403` → `'authorization'`
305
+ * - `404` → `'not_found'` `408` → `'timeout'` `429` → `'rate_limit'`
306
+ * - `5xx` → `'service'`
307
+ *
308
+ * Use `recoverable` to decide whether to retry, and `retryAfterMs` for
309
+ * the backoff hint on rate-limit responses.
310
+ *
311
+ * @example
312
+ * import { HttpStatusError } from '@pellux/goodvibes-sdk';
313
+ *
314
+ * try {
315
+ * await sdk.operator.agents.list();
316
+ * } catch (err) {
317
+ * if (err instanceof HttpStatusError) {
318
+ * if (err.category === 'rate_limit') {
319
+ * await delay(err.retryAfterMs ?? 1000);
320
+ * } else if (!err.recoverable) {
321
+ * throw err; // Surface non-retryable errors immediately
322
+ * }
323
+ * }
324
+ * }
325
+ */
81
326
  export class HttpStatusError extends GoodVibesSdkError {
327
+ static [Symbol.hasInstance](value) {
328
+ if (this !== HttpStatusError) {
329
+ return typeof value === 'object'
330
+ && value !== null
331
+ && this.prototype.isPrototypeOf(value);
332
+ }
333
+ if (typeof value !== 'object' || value === null)
334
+ return false;
335
+ const record = value;
336
+ return record.code === 'SDK_HTTP_STATUS_ERROR';
337
+ }
82
338
  constructor(message, options = {}) {
83
339
  super(message, {
84
340
  ...options,
@@ -90,18 +346,18 @@ export class HttpStatusError extends GoodVibesSdkError {
90
346
  export function isStructuredDaemonErrorBody(value) {
91
347
  return typeof value === 'object' && value !== null && typeof value.error === 'string';
92
348
  }
93
- export function createHttpStatusError(status, url, method, body) {
349
+ export function createHttpStatusError(status, url, method, body, fallbackHint) {
94
350
  if (isStructuredDaemonErrorBody(body)) {
95
351
  return new HttpStatusError(body.error, {
96
352
  code: body.code,
97
353
  category: body.category,
98
354
  source: body.source ?? 'transport',
99
355
  recoverable: body.recoverable,
100
- status: body.status ?? status,
356
+ status,
101
357
  url,
102
358
  method,
103
359
  body,
104
- hint: body.hint,
360
+ hint: body.hint ?? fallbackHint,
105
361
  provider: body.provider,
106
362
  operation: body.operation,
107
363
  phase: body.phase,
@@ -119,5 +375,6 @@ export function createHttpStatusError(status, url, method, body) {
119
375
  url,
120
376
  method,
121
377
  body,
378
+ hint: fallbackHint,
122
379
  });
123
380
  }
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-errors",
3
- "version": "0.18.3",
3
+ "version": "0.30.0",
4
+ "engines": {
5
+ "node": ">=20.0.0"
6
+ },
4
7
  "description": "Structured SDK, transport, and daemon error types for GoodVibes integrations.",
5
8
  "type": "module",
6
9
  "main": "./dist/index.js",
@@ -10,6 +13,10 @@
10
13
  "types": "./dist/index.d.ts",
11
14
  "import": "./dist/index.js"
12
15
  },
16
+ "./daemon-error-contract": {
17
+ "types": "./dist/daemon-error-contract.d.ts",
18
+ "import": "./dist/daemon-error-contract.js"
19
+ },
13
20
  "./package.json": "./package.json"
14
21
  },
15
22
  "files": [