@kaiban/sdk 0.3.0 → 0.3.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 CHANGED
@@ -286,6 +286,9 @@ import {
286
286
  ValidationError,
287
287
  UnauthorizedError,
288
288
  RateLimitError,
289
+ BadGatewayError,
290
+ GatewayTimeoutError,
291
+ UnavailableError,
289
292
  TimeoutError,
290
293
  AbortedError,
291
294
  } from '@kaiban/sdk';
@@ -300,22 +303,44 @@ try {
300
303
  } catch (err) {
301
304
  if (err instanceof NotFoundError) {
302
305
  console.error('Card not found:', err.message);
303
- console.error('Error code:', err.code); // Optional error code from API
306
+ console.error('Error code:', err.code); // 'not_found'
307
+ console.error('Request ID:', err.meta?.request_id);
308
+ console.error('Timestamp:', err.meta?.timestamp);
304
309
  } else if (err instanceof ValidationError) {
305
310
  console.error('Validation failed:', err.message);
306
- console.error('Details:', err.details); // Additional error details from API
311
+ console.error('Error code:', err.code); // 'validation_error'
312
+ // ValidationError.details includes issues array
313
+ if (err.details?.issues) {
314
+ err.details.issues.forEach((issue) => {
315
+ console.error(` - ${issue.path.join('.')}: ${issue.message}`);
316
+ });
317
+ }
307
318
  } else if (err instanceof BadRequestError) {
308
319
  console.error('Invalid request:', err.message);
320
+ console.error('Error code:', err.code); // 'invalid_argument'
309
321
  } else if (err instanceof UnauthorizedError) {
310
322
  console.error('Authentication required:', err.message);
323
+ console.error('Error code:', err.code); // 'unauthenticated'
311
324
  } else if (err instanceof RateLimitError) {
312
325
  console.error('Rate limit exceeded:', err.message);
326
+ console.error('Error code:', err.code); // 'rate_limit_exceeded'
327
+ } else if (err instanceof BadGatewayError) {
328
+ console.error('Bad gateway:', err.message);
329
+ console.error('Error code:', err.code); // 'bad_gateway'
330
+ } else if (err instanceof GatewayTimeoutError) {
331
+ console.error('Gateway timeout:', err.message);
332
+ console.error('Error code:', err.code); // 'gateway_timeout'
333
+ } else if (err instanceof UnavailableError) {
334
+ console.error('Service unavailable:', err.message);
335
+ console.error('Error code:', err.code); // 'service_unavailable'
313
336
  } else if (err instanceof TimeoutError) {
314
337
  console.error('Request timed out');
315
338
  } else if (err instanceof AbortedError) {
316
339
  console.error('Request was cancelled');
317
340
  } else if (err instanceof ApiError) {
318
341
  console.error('API error:', err.message);
342
+ console.error('Error code:', err.code);
343
+ console.error('Details:', err.details);
319
344
  } else {
320
345
  console.error('Unexpected error:', err);
321
346
  }
@@ -324,20 +349,29 @@ try {
324
349
 
325
350
  ### Available Error Classes
326
351
 
327
- - **`ApiError`**: Base class for all API errors (includes `code`, `message`, `details` properties)
328
- - **`BadRequestError`**: 400 - Invalid request format or parameters
329
- - **`ValidationError`**: 422 - Request validation failed
330
- - **`UnauthorizedError`**: 401 - Authentication required or failed
331
- - **`ForbiddenError`**: 403 - Insufficient permissions
332
- - **`NotFoundError`**: 404 - Resource not found
333
- - **`ConflictError`**: 409 - Resource conflict
334
- - **`RateLimitError`**: 429 - Too many requests
335
- - **`UnavailableError`**: 502, 503, 504 - Service temporarily unavailable
336
- - **`ServerError`**: 500 or other server errors
352
+ - **`ApiError`**: Base class for all API errors (includes `code`, `message`, `details`, `meta` properties)
353
+ - **`BadRequestError`**: 400 - Invalid request format or parameters (`invalid_argument`)
354
+ - **`ValidationError`**: 422 - Request validation failed (`validation_error`, includes `details.issues[]`)
355
+ - **`UnauthorizedError`**: 401 - Authentication required or failed (`unauthenticated`)
356
+ - **`ForbiddenError`**: 403 - Insufficient permissions (`permission_denied`)
357
+ - **`NotFoundError`**: 404 - Resource not found (`not_found`)
358
+ - **`ConflictError`**: 409 - Resource conflict (`conflict`)
359
+ - **`RateLimitError`**: 429 - Too many requests (`rate_limit_exceeded`)
360
+ - **`BadGatewayError`**: 502 - Bad gateway (`bad_gateway`)
361
+ - **`UnavailableError`**: 503 - Service temporarily unavailable (`service_unavailable`)
362
+ - **`GatewayTimeoutError`**: 504 - Gateway timeout (`gateway_timeout`)
363
+ - **`ServerError`**: 500 - Internal server error (`internal`)
337
364
  - **`TimeoutError`**: Request exceeded configured timeout
338
365
  - **`AbortedError`**: Request was cancelled via AbortSignal
339
366
  - **`HttpError`**: Low-level HTTP error (includes `status`, `url`, `body` properties)
340
367
 
368
+ All API errors include:
369
+
370
+ - `code`: Backend error code (e.g., `validation_error`, `not_found`)
371
+ - `message`: Human-readable error message
372
+ - `details`: Optional error details (for `ValidationError`, includes `issues[]` array)
373
+ - `meta`: Request metadata with `request_id?` and `timestamp`
374
+
341
375
  ## Examples by resource
342
376
 
343
377
  ### Agents
@@ -165,7 +165,11 @@ class HttpClient {
165
165
  if (err instanceof errors_1.RateLimitError)
166
166
  return retryCfg.retryOn?.includes(429) ?? false;
167
167
  if (err instanceof errors_1.UnavailableError)
168
- return retryCfg.retryOn?.some((c) => c === 502 || c === 503 || c === 504) ?? false;
168
+ return retryCfg.retryOn?.includes(503) ?? false;
169
+ if (err instanceof errors_1.BadGatewayError)
170
+ return retryCfg.retryOn?.includes(502) ?? false;
171
+ if (err instanceof errors_1.GatewayTimeoutError)
172
+ return retryCfg.retryOn?.includes(504) ?? false;
169
173
  if (err instanceof errors_1.ServerError)
170
174
  return retryCfg.retryOn?.includes(500) ?? false;
171
175
  return false;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServerError = exports.UnavailableError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.ValidationError = exports.BadRequestError = exports.ApiError = exports.AbortedError = exports.TimeoutError = exports.HttpError = void 0;
3
+ exports.ServerError = exports.GatewayTimeoutError = exports.BadGatewayError = exports.UnavailableError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.ValidationError = exports.BadRequestError = exports.ApiError = exports.AbortedError = exports.TimeoutError = exports.HttpError = void 0;
4
4
  exports.mapHttpToSdkError = mapHttpToSdkError;
5
5
  class HttpError extends Error {
6
6
  constructor(message, status, url, body) {
@@ -32,6 +32,7 @@ class ApiError extends Error {
32
32
  this.name = 'ApiError';
33
33
  this.code = shape.code;
34
34
  this.details = shape.details;
35
+ this.meta = shape.meta;
35
36
  }
36
37
  }
37
38
  exports.ApiError = ApiError;
@@ -46,6 +47,7 @@ class ValidationError extends ApiError {
46
47
  constructor(shape) {
47
48
  super(shape);
48
49
  this.name = 'ValidationError';
50
+ this.details = shape.details;
49
51
  }
50
52
  }
51
53
  exports.ValidationError = ValidationError;
@@ -91,6 +93,20 @@ class UnavailableError extends ApiError {
91
93
  }
92
94
  }
93
95
  exports.UnavailableError = UnavailableError;
96
+ class BadGatewayError extends ApiError {
97
+ constructor(shape) {
98
+ super(shape);
99
+ this.name = 'BadGatewayError';
100
+ }
101
+ }
102
+ exports.BadGatewayError = BadGatewayError;
103
+ class GatewayTimeoutError extends ApiError {
104
+ constructor(shape) {
105
+ super(shape);
106
+ this.name = 'GatewayTimeoutError';
107
+ }
108
+ }
109
+ exports.GatewayTimeoutError = GatewayTimeoutError;
94
110
  class ServerError extends ApiError {
95
111
  constructor(shape) {
96
112
  super(shape);
@@ -99,8 +115,38 @@ class ServerError extends ApiError {
99
115
  }
100
116
  exports.ServerError = ServerError;
101
117
  // Map HTTP response status and body to human-friendly SDK errors
118
+ // Uses backend error code when available, falls back to HTTP status mapping
102
119
  function mapHttpToSdkError(status, body) {
103
120
  const shape = normalizeBody(body);
121
+ // If backend provided an explicit error code, use it to determine error type
122
+ if (shape.code) {
123
+ switch (shape.code) {
124
+ case 'invalid_argument':
125
+ return new BadRequestError(shape);
126
+ case 'unauthenticated':
127
+ return new UnauthorizedError(shape);
128
+ case 'permission_denied':
129
+ return new ForbiddenError(shape);
130
+ case 'not_found':
131
+ return new NotFoundError(shape);
132
+ case 'conflict':
133
+ return new ConflictError(shape);
134
+ case 'validation_error':
135
+ return new ValidationError(shape);
136
+ case 'rate_limit_exceeded':
137
+ return new RateLimitError(shape);
138
+ case 'bad_gateway':
139
+ return new BadGatewayError(shape);
140
+ case 'service_unavailable':
141
+ return new UnavailableError(shape);
142
+ case 'gateway_timeout':
143
+ return new GatewayTimeoutError(shape);
144
+ case 'internal':
145
+ default:
146
+ return new ServerError(shape);
147
+ }
148
+ }
149
+ // Fallback to HTTP status code mapping if no backend code provided
104
150
  switch (status) {
105
151
  case 400:
106
152
  return new BadRequestError(shape);
@@ -117,14 +163,20 @@ function mapHttpToSdkError(status, body) {
117
163
  case 429:
118
164
  return new RateLimitError(shape);
119
165
  case 502:
166
+ return new BadGatewayError(shape);
120
167
  case 503:
121
- case 504:
122
168
  return new UnavailableError(shape);
169
+ case 504:
170
+ return new GatewayTimeoutError(shape);
123
171
  case 500:
124
172
  default:
125
173
  return new ServerError(shape);
126
174
  }
127
175
  }
176
+ /**
177
+ * Normalizes error response body to ApiErrorShape
178
+ * Backend returns: { error: { code, message, details? }, meta: { request_id?, timestamp } }
179
+ */
128
180
  function normalizeBody(body) {
129
181
  if (!body)
130
182
  return { message: 'Unknown error' };
@@ -132,12 +184,26 @@ function normalizeBody(body) {
132
184
  return { message: body };
133
185
  if (typeof body === 'object') {
134
186
  const anyBody = body;
135
- const code = anyBody['code'] || anyBody['error'];
187
+ // Backend structure: { error: { code, message, details? }, meta: { request_id?, timestamp } }
188
+ if (anyBody['error'] && typeof anyBody['error'] === 'object') {
189
+ const errorObj = anyBody['error'];
190
+ const meta = anyBody['meta'];
191
+ return {
192
+ code: errorObj['code'],
193
+ message: errorObj['message'] || 'Request failed',
194
+ details: errorObj['details'],
195
+ meta,
196
+ };
197
+ }
198
+ // Fallback: try to extract from flat structure (legacy support)
199
+ const code = anyBody['code'] ||
200
+ anyBody['error'];
136
201
  const message = anyBody['message'] ||
137
202
  anyBody['error_description'] ||
138
203
  'Request failed';
139
204
  const details = anyBody['details'];
140
- return { code, message, details };
205
+ const meta = anyBody['meta'];
206
+ return { code, message, details, meta };
141
207
  }
142
208
  return { message: 'Request failed' };
143
209
  }
@@ -1,4 +1,4 @@
1
- import { AbortedError, RateLimitError, ServerError, TimeoutError, UnavailableError, mapHttpToSdkError, } from './errors.js';
1
+ import { AbortedError, BadGatewayError, GatewayTimeoutError, RateLimitError, ServerError, TimeoutError, UnavailableError, mapHttpToSdkError, } from './errors.js';
2
2
  function buildQuery(query) {
3
3
  if (!query)
4
4
  return '';
@@ -162,7 +162,11 @@ export class HttpClient {
162
162
  if (err instanceof RateLimitError)
163
163
  return retryCfg.retryOn?.includes(429) ?? false;
164
164
  if (err instanceof UnavailableError)
165
- return retryCfg.retryOn?.some((c) => c === 502 || c === 503 || c === 504) ?? false;
165
+ return retryCfg.retryOn?.includes(503) ?? false;
166
+ if (err instanceof BadGatewayError)
167
+ return retryCfg.retryOn?.includes(502) ?? false;
168
+ if (err instanceof GatewayTimeoutError)
169
+ return retryCfg.retryOn?.includes(504) ?? false;
166
170
  if (err instanceof ServerError)
167
171
  return retryCfg.retryOn?.includes(500) ?? false;
168
172
  return false;
@@ -25,6 +25,7 @@ export class ApiError extends Error {
25
25
  this.name = 'ApiError';
26
26
  this.code = shape.code;
27
27
  this.details = shape.details;
28
+ this.meta = shape.meta;
28
29
  }
29
30
  }
30
31
  export class BadRequestError extends ApiError {
@@ -37,6 +38,7 @@ export class ValidationError extends ApiError {
37
38
  constructor(shape) {
38
39
  super(shape);
39
40
  this.name = 'ValidationError';
41
+ this.details = shape.details;
40
42
  }
41
43
  }
42
44
  export class UnauthorizedError extends ApiError {
@@ -75,6 +77,18 @@ export class UnavailableError extends ApiError {
75
77
  this.name = 'UnavailableError';
76
78
  }
77
79
  }
80
+ export class BadGatewayError extends ApiError {
81
+ constructor(shape) {
82
+ super(shape);
83
+ this.name = 'BadGatewayError';
84
+ }
85
+ }
86
+ export class GatewayTimeoutError extends ApiError {
87
+ constructor(shape) {
88
+ super(shape);
89
+ this.name = 'GatewayTimeoutError';
90
+ }
91
+ }
78
92
  export class ServerError extends ApiError {
79
93
  constructor(shape) {
80
94
  super(shape);
@@ -82,8 +96,38 @@ export class ServerError extends ApiError {
82
96
  }
83
97
  }
84
98
  // Map HTTP response status and body to human-friendly SDK errors
99
+ // Uses backend error code when available, falls back to HTTP status mapping
85
100
  export function mapHttpToSdkError(status, body) {
86
101
  const shape = normalizeBody(body);
102
+ // If backend provided an explicit error code, use it to determine error type
103
+ if (shape.code) {
104
+ switch (shape.code) {
105
+ case 'invalid_argument':
106
+ return new BadRequestError(shape);
107
+ case 'unauthenticated':
108
+ return new UnauthorizedError(shape);
109
+ case 'permission_denied':
110
+ return new ForbiddenError(shape);
111
+ case 'not_found':
112
+ return new NotFoundError(shape);
113
+ case 'conflict':
114
+ return new ConflictError(shape);
115
+ case 'validation_error':
116
+ return new ValidationError(shape);
117
+ case 'rate_limit_exceeded':
118
+ return new RateLimitError(shape);
119
+ case 'bad_gateway':
120
+ return new BadGatewayError(shape);
121
+ case 'service_unavailable':
122
+ return new UnavailableError(shape);
123
+ case 'gateway_timeout':
124
+ return new GatewayTimeoutError(shape);
125
+ case 'internal':
126
+ default:
127
+ return new ServerError(shape);
128
+ }
129
+ }
130
+ // Fallback to HTTP status code mapping if no backend code provided
87
131
  switch (status) {
88
132
  case 400:
89
133
  return new BadRequestError(shape);
@@ -100,14 +144,20 @@ export function mapHttpToSdkError(status, body) {
100
144
  case 429:
101
145
  return new RateLimitError(shape);
102
146
  case 502:
147
+ return new BadGatewayError(shape);
103
148
  case 503:
104
- case 504:
105
149
  return new UnavailableError(shape);
150
+ case 504:
151
+ return new GatewayTimeoutError(shape);
106
152
  case 500:
107
153
  default:
108
154
  return new ServerError(shape);
109
155
  }
110
156
  }
157
+ /**
158
+ * Normalizes error response body to ApiErrorShape
159
+ * Backend returns: { error: { code, message, details? }, meta: { request_id?, timestamp } }
160
+ */
111
161
  function normalizeBody(body) {
112
162
  if (!body)
113
163
  return { message: 'Unknown error' };
@@ -115,12 +165,26 @@ function normalizeBody(body) {
115
165
  return { message: body };
116
166
  if (typeof body === 'object') {
117
167
  const anyBody = body;
118
- const code = anyBody['code'] || anyBody['error'];
168
+ // Backend structure: { error: { code, message, details? }, meta: { request_id?, timestamp } }
169
+ if (anyBody['error'] && typeof anyBody['error'] === 'object') {
170
+ const errorObj = anyBody['error'];
171
+ const meta = anyBody['meta'];
172
+ return {
173
+ code: errorObj['code'],
174
+ message: errorObj['message'] || 'Request failed',
175
+ details: errorObj['details'],
176
+ meta,
177
+ };
178
+ }
179
+ // Fallback: try to extract from flat structure (legacy support)
180
+ const code = anyBody['code'] ||
181
+ anyBody['error'];
119
182
  const message = anyBody['message'] ||
120
183
  anyBody['error_description'] ||
121
184
  'Request failed';
122
185
  const details = anyBody['details'];
123
- return { code, message, details };
186
+ const meta = anyBody['meta'];
187
+ return { code, message, details, meta };
124
188
  }
125
189
  return { message: 'Request failed' };
126
190
  }
@@ -10,20 +10,38 @@ export declare class TimeoutError extends Error {
10
10
  export declare class AbortedError extends Error {
11
11
  constructor(message?: string);
12
12
  }
13
+ export type ApiErrorCode = 'invalid_argument' | 'not_found' | 'internal' | 'validation_error' | 'permission_denied' | 'unauthenticated' | 'conflict' | 'rate_limit_exceeded' | 'bad_gateway' | 'service_unavailable' | 'gateway_timeout';
14
+ export interface ValidationIssue {
15
+ code: string;
16
+ path: Array<string | number>;
17
+ message: string;
18
+ }
13
19
  export interface ApiErrorShape {
14
- code?: string;
20
+ code?: ApiErrorCode;
15
21
  message: string;
16
22
  details?: unknown;
23
+ meta?: {
24
+ request_id?: string;
25
+ timestamp: string;
26
+ };
17
27
  }
18
28
  export declare class ApiError extends Error {
19
- readonly code?: string;
29
+ readonly code?: ApiErrorCode;
20
30
  readonly details?: unknown;
31
+ readonly meta?: {
32
+ request_id?: string;
33
+ timestamp: string;
34
+ };
21
35
  constructor(shape: ApiErrorShape);
22
36
  }
23
37
  export declare class BadRequestError extends ApiError {
24
38
  constructor(shape: ApiErrorShape);
25
39
  }
26
40
  export declare class ValidationError extends ApiError {
41
+ readonly details?: {
42
+ issues?: ValidationIssue[];
43
+ [key: string]: unknown;
44
+ };
27
45
  constructor(shape: ApiErrorShape);
28
46
  }
29
47
  export declare class UnauthorizedError extends ApiError {
@@ -44,6 +62,12 @@ export declare class RateLimitError extends ApiError {
44
62
  export declare class UnavailableError extends ApiError {
45
63
  constructor(shape: ApiErrorShape);
46
64
  }
65
+ export declare class BadGatewayError extends ApiError {
66
+ constructor(shape: ApiErrorShape);
67
+ }
68
+ export declare class GatewayTimeoutError extends ApiError {
69
+ constructor(shape: ApiErrorShape);
70
+ }
47
71
  export declare class ServerError extends ApiError {
48
72
  constructor(shape: ApiErrorShape);
49
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaiban/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Official TypeScript SDK for the Kaiban API",
5
5
  "keywords": [
6
6
  "kaiban",