@pellux/goodvibes-errors 0.18.3 → 0.30.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/README.md +7 -5
- package/dist/daemon-error-contract.d.ts +17 -0
- package/dist/daemon-error-contract.d.ts.map +1 -1
- package/dist/daemon-error-contract.js +17 -1
- package/dist/index.d.ts +111 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +262 -5
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# @pellux/goodvibes-errors
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Internal workspace package backing `@pellux/goodvibes-sdk/errors`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Consumers should install `@pellux/goodvibes-sdk` and import this surface from the umbrella package.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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":"
|
|
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 {
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.30.1",
|
|
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": [
|