@pellux/goodvibes-errors 0.33.36 → 0.33.38

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
@@ -24,8 +24,106 @@ export type ErrorSource = DaemonErrorSource | 'contract';
24
24
  * }
25
25
  */
26
26
  export type SDKErrorKind = 'auth' | 'config' | 'contract' | 'network' | 'not-found' | 'protocol' | 'rate-limit' | 'service' | 'internal' | 'tool' | 'validation' | 'unknown';
27
+ /**
28
+ * Exhaustive string-literal union of the canonical error codes produced by the
29
+ * GoodVibes SDK. Use this type when you need to pattern-match on `err.code`
30
+ * without losing exhaustiveness checking.
31
+ *
32
+ * The `code` field on {@link GoodVibesSdkError} is typed as
33
+ * `SDKErrorCode | (string & {})` so that:
34
+ * - SDK-produced errors surface as one of the known literals (IDE autocomplete
35
+ * and exhaustive switches work).
36
+ * - Caller-supplied arbitrary string codes still type-check without casting.
37
+ *
38
+ * ### Consumer pattern
39
+ * ```ts
40
+ * import { isErrorCode, SDKErrorCodes } from '@pellux/goodvibes-errors';
41
+ *
42
+ * catch (err) {
43
+ * if (err instanceof GoodVibesSdkError) {
44
+ * if (isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
45
+ * await delay(err.retryAfterMs ?? 1000);
46
+ * } else if (isErrorCode(err, SDKErrorCodes.AUTH_REQUIRED)) {
47
+ * await refreshToken();
48
+ * } else if (isErrorCode(err, SDKErrorCodes.TOKEN_EXPIRED)) {
49
+ * await refreshToken();
50
+ * }
51
+ * }
52
+ * }
53
+ * ```
54
+ */
55
+ export type SDKErrorCode = 'AUTH_REQUIRED' | 'TOKEN_EXPIRED' | 'PERMISSION_DENIED' | 'PAYMENT_REQUIRED' | 'RATE_LIMITED' | 'NETWORK_UNREACHABLE' | 'TIMEOUT' | 'CANCELLED' | 'NOT_FOUND' | 'CONFLICT' | 'VALIDATION_FAILED' | 'AGENT_TIMEOUT' | 'AGENT_FAILED' | 'TOOL_EXEC_FAILED' | 'SERVICE_UNAVAILABLE' | 'CONTRACT_MISMATCH' | 'PROTOCOL_ERROR' | 'INTERNAL_ERROR' | 'SDK_CONFIGURATION_ERROR' | 'SDK_CONTRACT_ERROR' | 'SDK_HTTP_STATUS_ERROR' | 'UNKNOWN';
56
+ /**
57
+ * Runtime-accessible const object mirroring the {@link SDKErrorCode} union.
58
+ * Prefer referencing these constants over raw string literals for refactor safety.
59
+ *
60
+ * @example
61
+ * import { SDKErrorCodes } from '@pellux/goodvibes-errors';
62
+ *
63
+ * if (err.code === SDKErrorCodes.RATE_LIMITED) {
64
+ * await delay(err.retryAfterMs ?? 1000);
65
+ * }
66
+ */
67
+ export declare const SDKErrorCodes: {
68
+ readonly AUTH_REQUIRED: "AUTH_REQUIRED";
69
+ readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
70
+ readonly PERMISSION_DENIED: "PERMISSION_DENIED";
71
+ readonly PAYMENT_REQUIRED: "PAYMENT_REQUIRED";
72
+ readonly RATE_LIMITED: "RATE_LIMITED";
73
+ readonly NETWORK_UNREACHABLE: "NETWORK_UNREACHABLE";
74
+ readonly TIMEOUT: "TIMEOUT";
75
+ readonly CANCELLED: "CANCELLED";
76
+ readonly NOT_FOUND: "NOT_FOUND";
77
+ readonly CONFLICT: "CONFLICT";
78
+ readonly VALIDATION_FAILED: "VALIDATION_FAILED";
79
+ readonly AGENT_TIMEOUT: "AGENT_TIMEOUT";
80
+ readonly AGENT_FAILED: "AGENT_FAILED";
81
+ readonly TOOL_EXEC_FAILED: "TOOL_EXEC_FAILED";
82
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
83
+ readonly CONTRACT_MISMATCH: "CONTRACT_MISMATCH";
84
+ readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
85
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
86
+ readonly SDK_CONFIGURATION_ERROR: "SDK_CONFIGURATION_ERROR";
87
+ readonly SDK_CONTRACT_ERROR: "SDK_CONTRACT_ERROR";
88
+ readonly SDK_HTTP_STATUS_ERROR: "SDK_HTTP_STATUS_ERROR";
89
+ readonly UNKNOWN: "UNKNOWN";
90
+ };
91
+ /**
92
+ * Returns `true` when `err.code` equals the given {@link SDKErrorCode},
93
+ * narrowing the type of `err.code` to the specific literal.
94
+ *
95
+ * Works with any object that has a `code?: string` field — not limited to
96
+ * {@link GoodVibesSdkError} subclasses.
97
+ *
98
+ * @example
99
+ * import { isErrorCode, SDKErrorCodes, GoodVibesSdkError } from '@pellux/goodvibes-errors';
100
+ *
101
+ * if (err instanceof GoodVibesSdkError && isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
102
+ * console.log('retry after', err.retryAfterMs);
103
+ * }
104
+ *
105
+ * @param err - Any object with an optional `code` string field.
106
+ * @param code - The {@link SDKErrorCode} literal to match against.
107
+ */
108
+ export declare function isErrorCode<C extends SDKErrorCode>(err: {
109
+ readonly code?: SDKErrorCode | (string & {}) | undefined;
110
+ }, code: C): err is {
111
+ readonly code: C;
112
+ };
113
+ /**
114
+ * Returns `true` when `value` is a known {@link SDKErrorCode} string.
115
+ * Useful for discriminating structured errors received over the wire.
116
+ *
117
+ * @param value - The string to test.
118
+ */
119
+ export declare function isKnownErrorCode(value: string): value is SDKErrorCode;
27
120
  export interface GoodVibesSdkErrorOptions {
28
- readonly code?: string | undefined;
121
+ /**
122
+ * A typed error code for programmatic matching. May be an {@link SDKErrorCode}
123
+ * literal or any custom string for caller-supplied codes.
124
+ * When omitted, the SDK infers a code from `category` or `status`.
125
+ */
126
+ readonly code?: SDKErrorCode | (string & {}) | undefined;
29
127
  readonly category?: ErrorCategory | undefined;
30
128
  readonly source?: ErrorSource | undefined;
31
129
  readonly recoverable?: boolean | undefined;
@@ -47,10 +145,29 @@ export declare const RETRYABLE_STATUS_CODES: readonly number[];
47
145
  /**
48
146
  * Base error class for all errors thrown by the GoodVibes SDK.
49
147
  *
50
- * Every error carries a structured `category` and `source` that allow
148
+ * Every error carries a structured `category`, `source`, and `code` that allow
51
149
  * callers to handle specific failure modes without string-matching messages.
52
150
  *
53
- * ### Narrowing pattern
151
+ * The `code` field is typed as `SDKErrorCode | (string & {})` — SDK-produced
152
+ * errors always carry a known {@link SDKErrorCode}, while caller-supplied codes
153
+ * remain valid arbitrary strings.
154
+ *
155
+ * ### Narrowing by code
156
+ * ```ts
157
+ * import { GoodVibesSdkError, isErrorCode, SDKErrorCodes } from '@pellux/goodvibes-errors';
158
+ *
159
+ * catch (err) {
160
+ * if (err instanceof GoodVibesSdkError) {
161
+ * if (isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
162
+ * await delay(err.retryAfterMs ?? 1000);
163
+ * } else if (isErrorCode(err, SDKErrorCodes.TOKEN_EXPIRED)) {
164
+ * await refreshToken();
165
+ * }
166
+ * }
167
+ * }
168
+ * ```
169
+ *
170
+ * ### Narrowing by kind
54
171
  * ```ts
55
172
  * import { GoodVibesSdkError, HttpStatusError, ConfigurationError } from '@pellux/goodvibes-errors';
56
173
  *
@@ -69,7 +186,18 @@ export declare const RETRYABLE_STATUS_CODES: readonly number[];
69
186
  */
70
187
  export declare class GoodVibesSdkError extends Error {
71
188
  readonly kind: SDKErrorKind;
72
- readonly code?: string | undefined;
189
+ /**
190
+ * Typed error code for programmatic matching. SDK-produced errors always set
191
+ * a {@link SDKErrorCode}; caller-supplied codes may be any string.
192
+ *
193
+ * **Note:** `code` and `category` are inferred independently and can diverge.
194
+ * For example, `new GoodVibesSdkError('…', { status: 409 })` yields
195
+ * `code === 'CONFLICT'` (from `inferCodeFromStatus`) while
196
+ * `category === 'unknown'` (because `inferCategory` intentionally returns
197
+ * `'unknown'` for 409 — the caller must supply `category` explicitly to get
198
+ * a meaningful category for conflict-style errors).
199
+ */
200
+ readonly code: SDKErrorCode | (string & {});
73
201
  readonly category: ErrorCategory;
74
202
  readonly source: ErrorSource;
75
203
  readonly recoverable: boolean;
@@ -95,7 +223,7 @@ export declare class GoodVibesSdkError extends Error {
95
223
  * implementation available, or calling a mutation on a read-only auth resolver).
96
224
  *
97
225
  * Always non-recoverable (`recoverable: false`).
98
- * Category: `'config'`. Kind: `'config'`.
226
+ * Category: `'config'`. Kind: `'config'`. Code: `'SDK_CONFIGURATION_ERROR'`.
99
227
  *
100
228
  * @example
101
229
  * import { ConfigurationError } from '@pellux/goodvibes-errors';
@@ -124,7 +252,7 @@ export declare class ConfigurationError extends GoodVibesSdkError {
124
252
  * (unexpected shape, missing required fields, etc.).
125
253
  *
126
254
  * Always non-recoverable (`recoverable: false`).
127
- * Category: `'contract'`. Kind: `'contract'`.
255
+ * Category: `'contract'`. Kind: `'contract'`. Code: `'SDK_CONTRACT_ERROR'`.
128
256
  *
129
257
  * @example
130
258
  * import { ContractError } from '@pellux/goodvibes-errors';
@@ -158,6 +286,17 @@ export declare class ContractError extends GoodVibesSdkError {
158
286
  * - `5xx` → `'service'`
159
287
  * - Any other status (or when constructed without a `status`) → `'unknown'`
160
288
  *
289
+ * The `code` field is inferred from `status` automatically:
290
+ * - `400` → `'VALIDATION_FAILED'`
291
+ * - `401` → `'AUTH_REQUIRED'`
292
+ * - `402` → `'PAYMENT_REQUIRED'`
293
+ * - `403` → `'PERMISSION_DENIED'`
294
+ * - `404` → `'NOT_FOUND'`
295
+ * - `408` → `'TIMEOUT'`
296
+ * - `409` → `'CONFLICT'`
297
+ * - `429` → `'RATE_LIMITED'`
298
+ * - `5xx` → `'SERVICE_UNAVAILABLE'`
299
+ *
161
300
  * When constructed without a `status` argument (e.g. as a typed
162
301
  * wrapper around a structured daemon error that provides its own `category`),
163
302
  * the category defaults to `'unknown'`. Callers relying on category-based
@@ -185,15 +324,36 @@ export declare class ContractError extends GoodVibesSdkError {
185
324
  */
186
325
  export declare class HttpStatusError extends GoodVibesSdkError {
187
326
  /**
188
- * Brand contract `code` is the source of truth, not the prototype chain.
327
+ * Brand contract: `instanceof HttpStatusError` relies on a dedicated Symbol
328
+ * brand stamped in the constructor, enabling cross-realm identity checks that
329
+ * are independent of the `code` field.
330
+ *
189
331
  * A `GoodVibesSdkError` constructed directly with `code: 'SDK_HTTP_STATUS_ERROR'`
190
- * will pass `instanceof HttpStatusError` even if its prototype is only
191
- * `GoodVibesSdkError`. Callers that need strict prototype checking should use
332
+ * will also pass `instanceof HttpStatusError` for backward compatibility with
333
+ * callers that serialise/deserialise errors by code. Callers that need strict
334
+ * prototype checking should use
192
335
  * `Object.getPrototypeOf(err) === HttpStatusError.prototype` instead.
193
336
  */
194
337
  static [Symbol.hasInstance](value: unknown): boolean;
195
338
  constructor(message: string, options?: GoodVibesSdkErrorOptions);
196
339
  }
197
340
  export declare function isStructuredDaemonErrorBody(value: unknown): value is StructuredDaemonErrorBody;
341
+ /**
342
+ * Creates an {@link HttpStatusError} from an HTTP response.
343
+ *
344
+ * When `body` is a {@link StructuredDaemonErrorBody}, its fields are used
345
+ * directly (including any explicit `code`). When the body is unstructured,
346
+ * the `code` is inferred from `status` via status-based inference (`inferCodeFromStatus`).
347
+ *
348
+ * The structured body path respects the body-supplied `code` over status
349
+ * inference, preserving full backward compatibility for callers that supply
350
+ * custom codes in the daemon response.
351
+ *
352
+ * @param status - HTTP status code.
353
+ * @param url - Request URL.
354
+ * @param method - HTTP method.
355
+ * @param body - Parsed response body (may be structured or unstructured).
356
+ * @param fallbackHint - Human-readable hint when the body provides none.
357
+ */
198
358
  export declare function createHttpStatusError(status: number, url: string, method: string, body: unknown, fallbackHint?: string): HttpStatusError;
199
359
  //# 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":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EAC1B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,SAAS,GACT,WAAW,GACX,UAAU,GACV,YAAY,GACZ,SAAS,GACT,UAAU,GACV,MAAM,GACN,YAAY,GACZ,SAAS,CAAC;AAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,YAAY,GAEpB,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,kBAAkB,GAElB,cAAc,GAEd,qBAAqB,GACrB,SAAS,GACT,WAAW,GAEX,WAAW,GACX,UAAU,GAEV,mBAAmB,GAEnB,eAAe,GACf,cAAc,GAEd,kBAAkB,GAElB,qBAAqB,GAErB,mBAAmB,GACnB,gBAAgB,GAChB,gBAAgB,GAChB,yBAAyB,GACzB,oBAAoB,GACpB,uBAAuB,GAEvB,SAAS,CAAC;AAEd;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;CAuB6B,CAAC;AAQxD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAChD,GAAG,EAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,EACjE,IAAI,EAAE,CAAC,GACN,GAAG,IAAI;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CAAE,CAE7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,YAAY,CAErE;AAgED,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtC;AAED,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAAmC,CAAC;AAuFxF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAgB,IAAI,EAAE,YAAY,CAAC;IACnC;;;;;;;;;;OAUG;IACH,SAAgB,IAAI,EAAE,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACnD,SAAgB,QAAQ,EAAE,aAAa,CAAC;IACxC,SAAgB,MAAM,EAAE,WAAW,CAAC;IACpC,SAAgB,WAAW,EAAE,OAAO,CAAC;IACrC,SAAgB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,SAAgB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,SAAgB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,SAAgB,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,SAAgB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,SAAgB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,SAAgB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,SAAgB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,SAAgB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,SAAgB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,SAAgB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,SAAgB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,SAAyB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;WAErC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;gBAWjD,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;IAmCnE,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAwBlC;AA+BD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD;;;;;;OAMG;WACa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;gBAYjD,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;CASpE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,aAAc,SAAQ,iBAAiB;IAClD;;;;;;OAMG;WACa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;gBAYjD,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;CASpE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,eAAgB,SAAQ,iBAAiB;IACpD;;;;;;;;;;OAUG;WACa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;gBAcjD,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;CAgBpE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,yBAAyB,CAE9F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,eAAe,CA8CjB"}
package/dist/index.js CHANGED
@@ -1,4 +1,73 @@
1
1
  export { DaemonErrorCategory } from './daemon-error-contract.js';
2
+ /**
3
+ * Runtime-accessible const object mirroring the {@link SDKErrorCode} union.
4
+ * Prefer referencing these constants over raw string literals for refactor safety.
5
+ *
6
+ * @example
7
+ * import { SDKErrorCodes } from '@pellux/goodvibes-errors';
8
+ *
9
+ * if (err.code === SDKErrorCodes.RATE_LIMITED) {
10
+ * await delay(err.retryAfterMs ?? 1000);
11
+ * }
12
+ */
13
+ export const SDKErrorCodes = {
14
+ AUTH_REQUIRED: 'AUTH_REQUIRED',
15
+ TOKEN_EXPIRED: 'TOKEN_EXPIRED',
16
+ PERMISSION_DENIED: 'PERMISSION_DENIED',
17
+ PAYMENT_REQUIRED: 'PAYMENT_REQUIRED',
18
+ RATE_LIMITED: 'RATE_LIMITED',
19
+ NETWORK_UNREACHABLE: 'NETWORK_UNREACHABLE',
20
+ TIMEOUT: 'TIMEOUT',
21
+ CANCELLED: 'CANCELLED',
22
+ NOT_FOUND: 'NOT_FOUND',
23
+ CONFLICT: 'CONFLICT',
24
+ VALIDATION_FAILED: 'VALIDATION_FAILED',
25
+ AGENT_TIMEOUT: 'AGENT_TIMEOUT',
26
+ AGENT_FAILED: 'AGENT_FAILED',
27
+ TOOL_EXEC_FAILED: 'TOOL_EXEC_FAILED',
28
+ SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
29
+ CONTRACT_MISMATCH: 'CONTRACT_MISMATCH',
30
+ PROTOCOL_ERROR: 'PROTOCOL_ERROR',
31
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
32
+ SDK_CONFIGURATION_ERROR: 'SDK_CONFIGURATION_ERROR',
33
+ SDK_CONTRACT_ERROR: 'SDK_CONTRACT_ERROR',
34
+ SDK_HTTP_STATUS_ERROR: 'SDK_HTTP_STATUS_ERROR',
35
+ UNKNOWN: 'UNKNOWN',
36
+ };
37
+ /**
38
+ * The set of known {@link SDKErrorCode} values for runtime membership tests.
39
+ * @internal
40
+ */
41
+ const SDK_ERROR_CODE_SET = new Set(Object.values(SDKErrorCodes));
42
+ /**
43
+ * Returns `true` when `err.code` equals the given {@link SDKErrorCode},
44
+ * narrowing the type of `err.code` to the specific literal.
45
+ *
46
+ * Works with any object that has a `code?: string` field — not limited to
47
+ * {@link GoodVibesSdkError} subclasses.
48
+ *
49
+ * @example
50
+ * import { isErrorCode, SDKErrorCodes, GoodVibesSdkError } from '@pellux/goodvibes-errors';
51
+ *
52
+ * if (err instanceof GoodVibesSdkError && isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
53
+ * console.log('retry after', err.retryAfterMs);
54
+ * }
55
+ *
56
+ * @param err - Any object with an optional `code` string field.
57
+ * @param code - The {@link SDKErrorCode} literal to match against.
58
+ */
59
+ export function isErrorCode(err, code) {
60
+ return err.code === code;
61
+ }
62
+ /**
63
+ * Returns `true` when `value` is a known {@link SDKErrorCode} string.
64
+ * Useful for discriminating structured errors received over the wire.
65
+ *
66
+ * @param value - The string to test.
67
+ */
68
+ export function isKnownErrorCode(value) {
69
+ return SDK_ERROR_CODE_SET.has(value);
70
+ }
2
71
  function inferKind(category) {
3
72
  switch (category) {
4
73
  case 'authentication':
@@ -32,6 +101,33 @@ function inferKind(category) {
32
101
  return 'unknown';
33
102
  }
34
103
  }
104
+ /**
105
+ * Infers the canonical {@link SDKErrorCode} from an {@link ErrorCategory}.
106
+ * Used internally to populate `code` when no explicit code is provided.
107
+ * @internal
108
+ */
109
+ function inferCodeFromCategory(category) {
110
+ switch (category) {
111
+ case 'authentication': return 'AUTH_REQUIRED';
112
+ case 'authorization': return 'PERMISSION_DENIED';
113
+ case 'billing': return 'PAYMENT_REQUIRED';
114
+ case 'permission': return 'PERMISSION_DENIED';
115
+ case 'config': return 'SDK_CONFIGURATION_ERROR';
116
+ case 'contract': return 'CONTRACT_MISMATCH';
117
+ case 'network': return 'NETWORK_UNREACHABLE';
118
+ case 'timeout': return 'TIMEOUT';
119
+ case 'not_found': return 'NOT_FOUND';
120
+ case 'rate_limit': return 'RATE_LIMITED';
121
+ case 'protocol': return 'PROTOCOL_ERROR';
122
+ case 'internal': return 'INTERNAL_ERROR';
123
+ case 'service': return 'SERVICE_UNAVAILABLE';
124
+ case 'bad_request': return 'VALIDATION_FAILED';
125
+ case 'tool': return 'TOOL_EXEC_FAILED';
126
+ case 'unknown':
127
+ default:
128
+ return 'UNKNOWN';
129
+ }
130
+ }
35
131
  export const RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504];
36
132
  function inferCategory(status) {
37
133
  if (status === 400)
@@ -46,12 +142,37 @@ function inferCategory(status) {
46
142
  return 'not_found';
47
143
  if (status === 408)
48
144
  return 'timeout';
145
+ if (status === 409)
146
+ return 'unknown'; // 409 Conflict — caller must supply category explicitly
49
147
  if (status === 429)
50
148
  return 'rate_limit';
51
149
  if (status !== undefined && status >= 500)
52
150
  return 'service';
53
151
  return 'unknown';
54
152
  }
153
+ /**
154
+ * Infers the canonical {@link SDKErrorCode} for HTTP status codes that have a
155
+ * direct 1-to-1 mapping, used when no explicit code is present in the response
156
+ * body. Returns `undefined` when the code should fall through to category-based
157
+ * inference (e.g. for structured-body responses that supply their own category).
158
+ * @internal
159
+ */
160
+ function inferCodeFromStatus(status) {
161
+ switch (status) {
162
+ case 400: return 'VALIDATION_FAILED';
163
+ case 401: return 'AUTH_REQUIRED';
164
+ case 402: return 'PAYMENT_REQUIRED';
165
+ case 403: return 'PERMISSION_DENIED';
166
+ case 404: return 'NOT_FOUND';
167
+ case 408: return 'TIMEOUT';
168
+ case 409: return 'CONFLICT';
169
+ case 429: return 'RATE_LIMITED';
170
+ default:
171
+ if (status >= 500)
172
+ return 'SERVICE_UNAVAILABLE';
173
+ return undefined;
174
+ }
175
+ }
55
176
  const ERROR_CATEGORIES = new Set([
56
177
  'authentication',
57
178
  'authorization',
@@ -71,6 +192,7 @@ const ERROR_CATEGORIES = new Set([
71
192
  'unknown',
72
193
  ]);
73
194
  const GOODVIBES_SDK_ERROR_BRAND = Symbol.for('pellux.goodvibes.sdk.error');
195
+ const HTTP_STATUS_ERROR_BRAND = Symbol.for('pellux.goodvibes.sdk.http-status-error');
74
196
  function readErrorCategory(value) {
75
197
  return typeof value === 'string' && ERROR_CATEGORIES.has(value)
76
198
  ? value
@@ -97,10 +219,29 @@ function inferCategoryFromCause(cause, seen = new Set(), depth = 0) {
97
219
  /**
98
220
  * Base error class for all errors thrown by the GoodVibes SDK.
99
221
  *
100
- * Every error carries a structured `category` and `source` that allow
222
+ * Every error carries a structured `category`, `source`, and `code` that allow
101
223
  * callers to handle specific failure modes without string-matching messages.
102
224
  *
103
- * ### Narrowing pattern
225
+ * The `code` field is typed as `SDKErrorCode | (string & {})` — SDK-produced
226
+ * errors always carry a known {@link SDKErrorCode}, while caller-supplied codes
227
+ * remain valid arbitrary strings.
228
+ *
229
+ * ### Narrowing by code
230
+ * ```ts
231
+ * import { GoodVibesSdkError, isErrorCode, SDKErrorCodes } from '@pellux/goodvibes-errors';
232
+ *
233
+ * catch (err) {
234
+ * if (err instanceof GoodVibesSdkError) {
235
+ * if (isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
236
+ * await delay(err.retryAfterMs ?? 1000);
237
+ * } else if (isErrorCode(err, SDKErrorCodes.TOKEN_EXPIRED)) {
238
+ * await refreshToken();
239
+ * }
240
+ * }
241
+ * }
242
+ * ```
243
+ *
244
+ * ### Narrowing by kind
104
245
  * ```ts
105
246
  * import { GoodVibesSdkError, HttpStatusError, ConfigurationError } from '@pellux/goodvibes-errors';
106
247
  *
@@ -119,6 +260,17 @@ function inferCategoryFromCause(cause, seen = new Set(), depth = 0) {
119
260
  */
120
261
  export class GoodVibesSdkError extends Error {
121
262
  kind;
263
+ /**
264
+ * Typed error code for programmatic matching. SDK-produced errors always set
265
+ * a {@link SDKErrorCode}; caller-supplied codes may be any string.
266
+ *
267
+ * **Note:** `code` and `category` are inferred independently and can diverge.
268
+ * For example, `new GoodVibesSdkError('…', { status: 409 })` yields
269
+ * `code === 'CONFLICT'` (from `inferCodeFromStatus`) while
270
+ * `category === 'unknown'` (because `inferCategory` intentionally returns
271
+ * `'unknown'` for 409 — the caller must supply `category` explicitly to get
272
+ * a meaningful category for conflict-style errors).
273
+ */
122
274
  code;
123
275
  category;
124
276
  source;
@@ -158,7 +310,11 @@ export class GoodVibesSdkError extends Error {
158
310
  enumerable: false,
159
311
  configurable: false,
160
312
  });
161
- this.code = options.code;
313
+ // Infer code from status first (most specific), then from category.
314
+ // Explicit caller-supplied code always wins.
315
+ this.code = options.code
316
+ ?? (options.status !== undefined ? inferCodeFromStatus(options.status) : undefined)
317
+ ?? inferCodeFromCategory(category);
162
318
  this.category = category;
163
319
  this.kind = inferKind(this.category);
164
320
  this.source = options.source ?? 'unknown';
@@ -238,7 +394,7 @@ function omitUndefined(record) {
238
394
  * implementation available, or calling a mutation on a read-only auth resolver).
239
395
  *
240
396
  * Always non-recoverable (`recoverable: false`).
241
- * Category: `'config'`. Kind: `'config'`.
397
+ * Category: `'config'`. Kind: `'config'`. Code: `'SDK_CONFIGURATION_ERROR'`.
242
398
  *
243
399
  * @example
244
400
  * import { ConfigurationError } from '@pellux/goodvibes-errors';
@@ -285,7 +441,7 @@ export class ConfigurationError extends GoodVibesSdkError {
285
441
  * (unexpected shape, missing required fields, etc.).
286
442
  *
287
443
  * Always non-recoverable (`recoverable: false`).
288
- * Category: `'contract'`. Kind: `'contract'`.
444
+ * Category: `'contract'`. Kind: `'contract'`. Code: `'SDK_CONTRACT_ERROR'`.
289
445
  *
290
446
  * @example
291
447
  * import { ContractError } from '@pellux/goodvibes-errors';
@@ -337,6 +493,17 @@ export class ContractError extends GoodVibesSdkError {
337
493
  * - `5xx` → `'service'`
338
494
  * - Any other status (or when constructed without a `status`) → `'unknown'`
339
495
  *
496
+ * The `code` field is inferred from `status` automatically:
497
+ * - `400` → `'VALIDATION_FAILED'`
498
+ * - `401` → `'AUTH_REQUIRED'`
499
+ * - `402` → `'PAYMENT_REQUIRED'`
500
+ * - `403` → `'PERMISSION_DENIED'`
501
+ * - `404` → `'NOT_FOUND'`
502
+ * - `408` → `'TIMEOUT'`
503
+ * - `409` → `'CONFLICT'`
504
+ * - `429` → `'RATE_LIMITED'`
505
+ * - `5xx` → `'SERVICE_UNAVAILABLE'`
506
+ *
340
507
  * When constructed without a `status` argument (e.g. as a typed
341
508
  * wrapper around a structured daemon error that provides its own `category`),
342
509
  * the category defaults to `'unknown'`. Callers relying on category-based
@@ -364,10 +531,14 @@ export class ContractError extends GoodVibesSdkError {
364
531
  */
365
532
  export class HttpStatusError extends GoodVibesSdkError {
366
533
  /**
367
- * Brand contract `code` is the source of truth, not the prototype chain.
534
+ * Brand contract: `instanceof HttpStatusError` relies on a dedicated Symbol
535
+ * brand stamped in the constructor, enabling cross-realm identity checks that
536
+ * are independent of the `code` field.
537
+ *
368
538
  * A `GoodVibesSdkError` constructed directly with `code: 'SDK_HTTP_STATUS_ERROR'`
369
- * will pass `instanceof HttpStatusError` even if its prototype is only
370
- * `GoodVibesSdkError`. Callers that need strict prototype checking should use
539
+ * will also pass `instanceof HttpStatusError` for backward compatibility with
540
+ * callers that serialise/deserialise errors by code. Callers that need strict
541
+ * prototype checking should use
371
542
  * `Object.getPrototypeOf(err) === HttpStatusError.prototype` instead.
372
543
  */
373
544
  static [Symbol.hasInstance](value) {
@@ -376,26 +547,65 @@ export class HttpStatusError extends GoodVibesSdkError {
376
547
  && value !== null
377
548
  && this.prototype.isPrototypeOf(value);
378
549
  }
379
- // Require both the brand (real SDK error instance) and matching code
380
- // to prevent plain objects like { code: 'SDK_HTTP_STATUS_ERROR' } from passing.
381
- return GoodVibesSdkError[Symbol.hasInstance](value)
382
- && value.code === 'SDK_HTTP_STATUS_ERROR';
550
+ if (!GoodVibesSdkError[Symbol.hasInstance](value))
551
+ return false;
552
+ const record = value;
553
+ // Primary: dedicated brand symbol (set in constructor — works in same realm).
554
+ if (record[HTTP_STATUS_ERROR_BRAND] === true)
555
+ return true;
556
+ // Fallback: code-based brand for cross-realm / serialised-error compat.
557
+ return record.code === 'SDK_HTTP_STATUS_ERROR';
383
558
  }
384
559
  constructor(message, options = {}) {
385
560
  super(message, {
386
561
  ...options,
562
+ // Default code retains 'SDK_HTTP_STATUS_ERROR' when no explicit code is
563
+ // supplied. Use createHttpStatusError() for status-specific semantic codes.
387
564
  code: options.code ?? 'SDK_HTTP_STATUS_ERROR',
388
565
  source: options.source ?? 'transport',
389
566
  });
567
+ // Stamp the brand AFTER super() so the symbol is always present on instances
568
+ // produced by this constructor (regardless of which code was stored).
569
+ Object.defineProperty(this, HTTP_STATUS_ERROR_BRAND, {
570
+ value: true,
571
+ enumerable: false,
572
+ configurable: false,
573
+ });
390
574
  }
391
575
  }
392
576
  export function isStructuredDaemonErrorBody(value) {
393
577
  return typeof value === 'object' && value !== null && typeof value.error === 'string';
394
578
  }
579
+ /**
580
+ * Creates an {@link HttpStatusError} from an HTTP response.
581
+ *
582
+ * When `body` is a {@link StructuredDaemonErrorBody}, its fields are used
583
+ * directly (including any explicit `code`). When the body is unstructured,
584
+ * the `code` is inferred from `status` via status-based inference (`inferCodeFromStatus`).
585
+ *
586
+ * The structured body path respects the body-supplied `code` over status
587
+ * inference, preserving full backward compatibility for callers that supply
588
+ * custom codes in the daemon response.
589
+ *
590
+ * @param status - HTTP status code.
591
+ * @param url - Request URL.
592
+ * @param method - HTTP method.
593
+ * @param body - Parsed response body (may be structured or unstructured).
594
+ * @param fallbackHint - Human-readable hint when the body provides none.
595
+ */
395
596
  export function createHttpStatusError(status, url, method, body, fallbackHint) {
396
597
  if (isStructuredDaemonErrorBody(body)) {
598
+ // Code precedence (highest to lowest):
599
+ // 1. Explicit code in the body (daemon-supplied)
600
+ // 2. Category-derived code when body supplies a category (category is
601
+ // more semantically specific than the HTTP status in this case)
602
+ // 3. Status-derived code as final fallback
603
+ const structuredCode = body.code
604
+ ?? (body.category !== undefined ? inferCodeFromCategory(body.category) : undefined)
605
+ ?? inferCodeFromStatus(status)
606
+ ?? 'UNKNOWN';
397
607
  return new HttpStatusError(body.error, {
398
- code: body.code,
608
+ code: structuredCode,
399
609
  category: body.category,
400
610
  source: body.source ?? 'transport',
401
611
  recoverable: body.recoverable,
@@ -417,6 +627,9 @@ export function createHttpStatusError(status, url, method, body, fallbackHint) {
417
627
  ? body.trim()
418
628
  : `Request failed with status ${status}`;
419
629
  return new HttpStatusError(message, {
630
+ // Explicitly inject the status-inferred code so HttpStatusError's own
631
+ // default ('SDK_HTTP_STATUS_ERROR') does not suppress the specific code.
632
+ code: inferCodeFromStatus(status) ?? 'SDK_HTTP_STATUS_ERROR',
420
633
  status,
421
634
  url,
422
635
  method,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-errors",
3
- "version": "0.33.36",
3
+ "version": "0.33.38",
4
4
  "engines": {
5
5
  "bun": "1.3.10",
6
6
  "node": ">=22.0.0"