@thisispamela/sdk 1.0.1 → 1.0.3

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
@@ -25,6 +25,9 @@ const call = await client.createCall({
25
25
  to: '+1234567890',
26
26
  task: 'Order a large pizza for delivery',
27
27
  locale: 'en-US',
28
+ voice: 'female',
29
+ agent_name: 'Pamela',
30
+ caller_name: 'John from Acme',
28
31
  });
29
32
 
30
33
  console.log('Call created:', call.id);
@@ -76,7 +79,7 @@ API keys are created and managed through the Pamela Partner Portal or via the Pa
76
79
  -d '{"project_id": "optional-project-id", "key_prefix": "pk_live_"}'
77
80
  ```
78
81
  3. **Save your API key immediately** - the full key is only returned once during creation
79
- 4. **Use the key prefix** (`pk_live_` or `pk_test_`) to identify keys in your account
82
+ 4. **Use the key prefix** (`pk_live_`) to identify keys in your account
80
83
 
81
84
  ### Managing API Keys
82
85
 
@@ -86,8 +89,7 @@ API keys are created and managed through the Pamela Partner Portal or via the Pa
86
89
 
87
90
  ### API Key Format
88
91
 
89
- - **Live keys**: Start with `pk_live_` (for production use)
90
- - **Test keys**: Start with `pk_test_` (for development/testing)
92
+ - **Live keys**: Start with `pk_live_` (all API usage)
91
93
  - **Security**: Keys are hashed in the database. Store them securely and never commit them to version control.
92
94
 
93
95
  ## Subscription Requirements
@@ -112,29 +114,116 @@ Check subscription status using the Enterprise Partner API:
112
114
  - `POST /api/b2b/v1/partner/subscription/checkout` - Create checkout session
113
115
  - `POST /api/b2b/v1/partner/subscription/portal` - Access Customer Portal
114
116
 
115
- ## Error Codes
117
+ ## Error Handling
116
118
 
117
- ### Authentication Errors
119
+ The SDK provides typed exceptions for all API errors:
118
120
 
119
- - `401 Unauthorized`: Invalid or missing API key
120
- - `403 Forbidden`: Subscription inactive or expired
121
- - Check subscription status: `subscription_status` must be `"active"`
122
- - For `past_due`: Check `grace_period_ends_at` - access allowed during grace period
121
+ ```typescript
122
+ import {
123
+ PamelaClient,
124
+ PamelaError,
125
+ AuthenticationError,
126
+ SubscriptionError,
127
+ RateLimitError,
128
+ ValidationError,
129
+ CallError,
130
+ } from '@thisispamela/sdk';
131
+
132
+ const client = new PamelaClient({ apiKey: 'pk_live_your_key' });
133
+
134
+ try {
135
+ const call = await client.createCall({ to: '+1234567890', task: 'Test' });
136
+ } catch (e) {
137
+ if (e instanceof AuthenticationError) {
138
+ // 401: Invalid or missing API key
139
+ console.log('Auth failed:', e.message);
140
+ console.log('Error code:', e.errorCode);
141
+ } else if (e instanceof SubscriptionError) {
142
+ // 403: Subscription inactive or expired
143
+ if (e.errorCode === 7008) {
144
+ console.log('Grace period expired - update payment method');
145
+ } else {
146
+ console.log('Subscription issue:', e.message);
147
+ }
148
+ } else if (e instanceof RateLimitError) {
149
+ // 429: Rate limit exceeded
150
+ const retryAfter = e.details?.retry_after ?? 30;
151
+ console.log(`Rate limited, retry after ${retryAfter}s`);
152
+ } else if (e instanceof ValidationError) {
153
+ // 400/422: Invalid request parameters
154
+ console.log('Invalid request:', e.message);
155
+ console.log('Details:', e.details);
156
+ } else if (e instanceof CallError) {
157
+ // Call-specific errors
158
+ console.log('Call error:', e.message);
159
+ } else if (e instanceof PamelaError) {
160
+ // All other API errors
161
+ console.log(`API error ${e.errorCode}: ${e.message}`);
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Exception Hierarchy
167
+
168
+ All exceptions extend `PamelaError`:
169
+
170
+ ```
171
+ PamelaError (base)
172
+ ├── AuthenticationError // 401 errors
173
+ ├── SubscriptionError // 403 errors (subscription issues)
174
+ ├── RateLimitError // 429 errors
175
+ ├── ValidationError // 400/422 errors
176
+ └── CallError // Call-specific errors
177
+ ```
178
+
179
+ ### Exception Properties
180
+
181
+ All exceptions have:
182
+ - `message`: Human-readable error message
183
+ - `errorCode?`: Numeric error code (e.g., 7008 for subscription expired)
184
+ - `details?`: Object with additional context
185
+ - `statusCode?`: HTTP status code
186
+
187
+ ## Error Codes Reference
188
+
189
+ ### Authentication Errors (401)
190
+
191
+ | Code | Description |
192
+ |------|-------------|
193
+ | 1001 | API key required |
194
+ | 1002 | Invalid API key |
195
+ | 1003 | API key expired |
196
+
197
+ ### Subscription Errors (403)
198
+
199
+ | Code | Description |
200
+ |------|-------------|
201
+ | 1005 | Enterprise subscription required |
202
+ | 7008 | Subscription expired (grace period ended) |
123
203
 
124
- ### Validation Errors
204
+ ### Validation Errors (400)
125
205
 
126
- - `400 Bad Request`: Invalid request parameters
127
- - `404 Not Found`: Resource not found (call, partner, etc.)
206
+ | Code | Description |
207
+ |------|-------------|
208
+ | 2001 | Validation error |
209
+ | 2002 | Invalid phone number format |
128
210
 
129
- ### Quota/Limit Errors
211
+ ### Enterprise Errors (7xxx)
130
212
 
131
- - `429 Too Many Requests`: Quota exceeded (if quota limits are set)
132
- - Note: Enterprise subscriptions have no quota limits for API calls (all usage is billed per-minute)
213
+ | Code | Description |
214
+ |------|-------------|
215
+ | 7001 | Partner not found |
216
+ | 7002 | Project not found |
217
+ | 7003 | Call not found |
218
+ | 7004 | No phone number for country |
219
+ | 7005 | Unsupported country |
133
220
 
134
- ### Rate Limit Errors
221
+ ### Rate Limiting (429)
135
222
 
136
- - `429 Too Many Requests`: Rate limit exceeded
137
- - Retry after the time specified in `Retry-After` header
223
+ | Code | Description |
224
+ |------|-------------|
225
+ | 6001 | Rate limit exceeded |
226
+ | 6002 | Quota exceeded |
138
227
 
139
228
  ## Usage Limits & Billing
140
229
 
@@ -0,0 +1,45 @@
1
+ export declare class PamelaError extends Error {
2
+ errorCode?: number;
3
+ details?: Record<string, any>;
4
+ statusCode?: number;
5
+ constructor(message: string, options?: {
6
+ errorCode?: number;
7
+ details?: Record<string, any>;
8
+ statusCode?: number;
9
+ });
10
+ }
11
+ export declare class AuthenticationError extends PamelaError {
12
+ constructor(message: string, options?: {
13
+ errorCode?: number;
14
+ details?: Record<string, any>;
15
+ statusCode?: number;
16
+ });
17
+ }
18
+ export declare class SubscriptionError extends PamelaError {
19
+ constructor(message: string, options?: {
20
+ errorCode?: number;
21
+ details?: Record<string, any>;
22
+ statusCode?: number;
23
+ });
24
+ }
25
+ export declare class RateLimitError extends PamelaError {
26
+ constructor(message: string, options?: {
27
+ errorCode?: number;
28
+ details?: Record<string, any>;
29
+ statusCode?: number;
30
+ });
31
+ }
32
+ export declare class ValidationError extends PamelaError {
33
+ constructor(message: string, options?: {
34
+ errorCode?: number;
35
+ details?: Record<string, any>;
36
+ statusCode?: number;
37
+ });
38
+ }
39
+ export declare class CallError extends PamelaError {
40
+ constructor(message: string, options?: {
41
+ errorCode?: number;
42
+ details?: Record<string, any>;
43
+ statusCode?: number;
44
+ });
45
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallError = exports.ValidationError = exports.RateLimitError = exports.SubscriptionError = exports.AuthenticationError = exports.PamelaError = void 0;
4
+ class PamelaError extends Error {
5
+ constructor(message, options) {
6
+ super(message);
7
+ this.name = 'PamelaError';
8
+ this.errorCode = options?.errorCode;
9
+ this.details = options?.details;
10
+ this.statusCode = options?.statusCode;
11
+ }
12
+ }
13
+ exports.PamelaError = PamelaError;
14
+ class AuthenticationError extends PamelaError {
15
+ constructor(message, options) {
16
+ super(message, options);
17
+ this.name = 'AuthenticationError';
18
+ }
19
+ }
20
+ exports.AuthenticationError = AuthenticationError;
21
+ class SubscriptionError extends PamelaError {
22
+ constructor(message, options) {
23
+ super(message, options);
24
+ this.name = 'SubscriptionError';
25
+ }
26
+ }
27
+ exports.SubscriptionError = SubscriptionError;
28
+ class RateLimitError extends PamelaError {
29
+ constructor(message, options) {
30
+ super(message, options);
31
+ this.name = 'RateLimitError';
32
+ }
33
+ }
34
+ exports.RateLimitError = RateLimitError;
35
+ class ValidationError extends PamelaError {
36
+ constructor(message, options) {
37
+ super(message, options);
38
+ this.name = 'ValidationError';
39
+ }
40
+ }
41
+ exports.ValidationError = ValidationError;
42
+ class CallError extends PamelaError {
43
+ constructor(message, options) {
44
+ super(message, options);
45
+ this.name = 'CallError';
46
+ }
47
+ }
48
+ exports.CallError = CallError;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Pamela Enterprise Voice API SDK for JavaScript/TypeScript
3
3
  */
4
4
  import { AxiosInstance } from 'axios';
5
+ import { PamelaError, AuthenticationError, SubscriptionError, RateLimitError, ValidationError, CallError } from './errors';
5
6
  export interface PamelaClientConfig {
6
7
  apiKey: string;
7
8
  baseUrl?: string;
@@ -12,6 +13,9 @@ export interface CreateCallRequest {
12
13
  locale?: string;
13
14
  task: string;
14
15
  instructions?: string;
16
+ voice?: 'male' | 'female' | 'auto';
17
+ agent_name?: string;
18
+ caller_name?: string;
15
19
  end_user_id?: string;
16
20
  metadata?: Record<string, any>;
17
21
  tools?: Array<Record<string, any>>;
@@ -150,3 +154,4 @@ export declare class PamelaClient {
150
154
  }
151
155
  export default PamelaClient;
152
156
  export { PamelaClient as Pamela };
157
+ export { PamelaError, AuthenticationError, SubscriptionError, RateLimitError, ValidationError, CallError, };
package/dist/index.js CHANGED
@@ -39,9 +39,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.Pamela = exports.PamelaClient = exports.UsageClient = void 0;
42
+ exports.CallError = exports.ValidationError = exports.RateLimitError = exports.SubscriptionError = exports.AuthenticationError = exports.PamelaError = exports.Pamela = exports.PamelaClient = exports.UsageClient = void 0;
43
43
  const axios_1 = __importDefault(require("axios"));
44
44
  const crypto = __importStar(require("crypto"));
45
+ const errors_1 = require("./errors");
46
+ Object.defineProperty(exports, "PamelaError", { enumerable: true, get: function () { return errors_1.PamelaError; } });
47
+ Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_1.AuthenticationError; } });
48
+ Object.defineProperty(exports, "SubscriptionError", { enumerable: true, get: function () { return errors_1.SubscriptionError; } });
49
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } });
50
+ Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_1.ValidationError; } });
51
+ Object.defineProperty(exports, "CallError", { enumerable: true, get: function () { return errors_1.CallError; } });
52
+ const mapAxiosError = (error, endpoint) => {
53
+ const statusCode = error.response?.status;
54
+ const data = error.response?.data;
55
+ let errorCode;
56
+ let message;
57
+ let details;
58
+ if (data && typeof data === 'object') {
59
+ const detail = data.detail;
60
+ if (detail && typeof detail === 'object') {
61
+ errorCode = detail.error_code ?? detail.error?.code;
62
+ message = detail.message ?? detail.error?.message;
63
+ details = detail.details ?? detail.error?.details;
64
+ }
65
+ else {
66
+ errorCode = data.error_code ?? data.error?.code;
67
+ message = data.message ?? data.detail;
68
+ details = data.details ?? data.error?.details;
69
+ }
70
+ }
71
+ if (!message) {
72
+ message = error.message || 'Request failed';
73
+ }
74
+ const options = { errorCode, details, statusCode };
75
+ if (statusCode === 401)
76
+ return new errors_1.AuthenticationError(message, options);
77
+ if (statusCode === 403)
78
+ return new errors_1.SubscriptionError(message, options);
79
+ if (statusCode === 429)
80
+ return new errors_1.RateLimitError(message, options);
81
+ if (statusCode === 400 || statusCode === 422)
82
+ return new errors_1.ValidationError(message, options);
83
+ if (endpoint?.startsWith('/calls')) {
84
+ return new errors_1.CallError(message, options);
85
+ }
86
+ return new errors_1.PamelaError(message, options);
87
+ };
45
88
  class UsageClient {
46
89
  constructor(client) {
47
90
  this.client = client;
@@ -81,7 +124,7 @@ class PamelaClient {
81
124
  await new Promise(resolve => setTimeout(resolve, 1000 * config.retry));
82
125
  return this.client.request(config);
83
126
  }
84
- return Promise.reject(error);
127
+ return Promise.reject(mapAxiosError(error, config?.url));
85
128
  });
86
129
  }
87
130
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thisispamela/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Pamela Enterprise Voice API SDK for JavaScript/TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/errors.ts ADDED
@@ -0,0 +1,48 @@
1
+ export class PamelaError extends Error {
2
+ public errorCode?: number;
3
+ public details?: Record<string, any>;
4
+ public statusCode?: number;
5
+
6
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
7
+ super(message);
8
+ this.name = 'PamelaError';
9
+ this.errorCode = options?.errorCode;
10
+ this.details = options?.details;
11
+ this.statusCode = options?.statusCode;
12
+ }
13
+ }
14
+
15
+ export class AuthenticationError extends PamelaError {
16
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
17
+ super(message, options);
18
+ this.name = 'AuthenticationError';
19
+ }
20
+ }
21
+
22
+ export class SubscriptionError extends PamelaError {
23
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
24
+ super(message, options);
25
+ this.name = 'SubscriptionError';
26
+ }
27
+ }
28
+
29
+ export class RateLimitError extends PamelaError {
30
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
31
+ super(message, options);
32
+ this.name = 'RateLimitError';
33
+ }
34
+ }
35
+
36
+ export class ValidationError extends PamelaError {
37
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
38
+ super(message, options);
39
+ this.name = 'ValidationError';
40
+ }
41
+ }
42
+
43
+ export class CallError extends PamelaError {
44
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
45
+ super(message, options);
46
+ this.name = 'CallError';
47
+ }
48
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,14 @@
4
4
 
5
5
  import axios, { AxiosInstance, AxiosError } from 'axios';
6
6
  import * as crypto from 'crypto';
7
+ import {
8
+ PamelaError,
9
+ AuthenticationError,
10
+ SubscriptionError,
11
+ RateLimitError,
12
+ ValidationError,
13
+ CallError,
14
+ } from './errors';
7
15
 
8
16
  export interface PamelaClientConfig {
9
17
  apiKey: string;
@@ -16,6 +24,9 @@ export interface CreateCallRequest {
16
24
  locale?: string;
17
25
  task: string;
18
26
  instructions?: string;
27
+ voice?: 'male' | 'female' | 'auto';
28
+ agent_name?: string;
29
+ caller_name?: string;
19
30
  end_user_id?: string;
20
31
  metadata?: Record<string, any>;
21
32
  tools?: Array<Record<string, any>>;
@@ -64,6 +75,43 @@ export interface WebhookPayload {
64
75
  data: Record<string, any>;
65
76
  }
66
77
 
78
+ const mapAxiosError = (error: AxiosError, endpoint?: string): PamelaError => {
79
+ const statusCode = error.response?.status;
80
+ const data = error.response?.data as any;
81
+ let errorCode: number | undefined;
82
+ let message: string | undefined;
83
+ let details: Record<string, any> | undefined;
84
+
85
+ if (data && typeof data === 'object') {
86
+ const detail = data.detail;
87
+ if (detail && typeof detail === 'object') {
88
+ errorCode = detail.error_code ?? detail.error?.code;
89
+ message = detail.message ?? detail.error?.message;
90
+ details = detail.details ?? detail.error?.details;
91
+ } else {
92
+ errorCode = data.error_code ?? data.error?.code;
93
+ message = data.message ?? data.detail;
94
+ details = data.details ?? data.error?.details;
95
+ }
96
+ }
97
+
98
+ if (!message) {
99
+ message = error.message || 'Request failed';
100
+ }
101
+
102
+ const options = { errorCode, details, statusCode };
103
+ if (statusCode === 401) return new AuthenticationError(message, options);
104
+ if (statusCode === 403) return new SubscriptionError(message, options);
105
+ if (statusCode === 429) return new RateLimitError(message, options);
106
+ if (statusCode === 400 || statusCode === 422) return new ValidationError(message, options);
107
+
108
+ if (endpoint?.startsWith('/calls')) {
109
+ return new CallError(message, options);
110
+ }
111
+
112
+ return new PamelaError(message, options);
113
+ };
114
+
67
115
  export class UsageClient {
68
116
  private client: AxiosInstance;
69
117
 
@@ -127,7 +175,7 @@ export class PamelaClient {
127
175
  return this.client.request(config);
128
176
  }
129
177
 
130
- return Promise.reject(error);
178
+ return Promise.reject(mapAxiosError(error, config?.url));
131
179
  }
132
180
  );
133
181
  }
@@ -225,4 +273,12 @@ export class PamelaClient {
225
273
  // Export as both default and named export for flexibility
226
274
  export default PamelaClient;
227
275
  export { PamelaClient as Pamela };
276
+ export {
277
+ PamelaError,
278
+ AuthenticationError,
279
+ SubscriptionError,
280
+ RateLimitError,
281
+ ValidationError,
282
+ CallError,
283
+ };
228
284
 
@@ -1,20 +1,171 @@
1
1
  /**
2
- * Integration tests for Pamela JavaScript SDK.
2
+ * Tests for Pamela JavaScript/TypeScript SDK.
3
3
  *
4
- * These tests run against staging only when env vars are provided.
4
+ * Includes unit tests (no network) and integration tests (require env vars).
5
5
  */
6
6
 
7
- import PamelaClient from "../src/index";
7
+ import PamelaClient, {
8
+ PamelaError,
9
+ AuthenticationError,
10
+ SubscriptionError,
11
+ RateLimitError,
12
+ ValidationError,
13
+ CallError,
14
+ } from "../src/index";
15
+ import * as crypto from "crypto";
8
16
 
9
17
  const TEST_API_URL =
10
18
  process.env.PAMELA_API_URL || "https://pamela-dev.up.railway.app";
11
19
  const TEST_API_KEY = process.env.PAMELA_TEST_API_KEY;
12
20
  const SHOULD_RUN = Boolean(TEST_API_KEY);
13
21
 
22
+ // =============================================================================
23
+ // Unit Tests (No Network Required)
24
+ // =============================================================================
25
+
26
+ describe("Webhook Signature Verification", () => {
27
+ const secret = "test_secret_123";
28
+
29
+ it("verifies valid signature", () => {
30
+ const payload = { event: "call.completed", call_id: "call_123" };
31
+ const payloadStr = JSON.stringify(payload);
32
+ const signature = crypto
33
+ .createHmac("sha256", secret)
34
+ .update(payloadStr)
35
+ .digest("hex");
36
+
37
+ expect(
38
+ PamelaClient.verifyWebhookSignature(payload, signature, secret)
39
+ ).toBe(true);
40
+ });
41
+
42
+ it("rejects invalid signature", () => {
43
+ const payload = { event: "call.completed" };
44
+ expect(
45
+ PamelaClient.verifyWebhookSignature(payload, "invalid_sig", secret)
46
+ ).toBe(false);
47
+ });
48
+
49
+ it("handles string payload", () => {
50
+ const payloadStr = '{"test":"value"}';
51
+ const signature = crypto
52
+ .createHmac("sha256", secret)
53
+ .update(payloadStr)
54
+ .digest("hex");
55
+
56
+ expect(
57
+ PamelaClient.verifyWebhookSignature(payloadStr, signature, secret)
58
+ ).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe("Exception Classes", () => {
63
+ it("PamelaError has correct properties", () => {
64
+ const error = new PamelaError("Test error", {
65
+ errorCode: 1001,
66
+ details: { key: "value" },
67
+ statusCode: 403,
68
+ });
69
+
70
+ expect(error.message).toBe("Test error");
71
+ expect(error.errorCode).toBe(1001);
72
+ expect(error.details).toEqual({ key: "value" });
73
+ expect(error.statusCode).toBe(403);
74
+ expect(error.name).toBe("PamelaError");
75
+ });
76
+
77
+ it("AuthenticationError has correct name", () => {
78
+ const error = new AuthenticationError("Invalid API key");
79
+ expect(error.name).toBe("AuthenticationError");
80
+ expect(error instanceof PamelaError).toBe(true);
81
+ });
82
+
83
+ it("SubscriptionError has correct name", () => {
84
+ const error = new SubscriptionError("Subscription expired", {
85
+ errorCode: 7008,
86
+ });
87
+ expect(error.name).toBe("SubscriptionError");
88
+ expect(error.errorCode).toBe(7008);
89
+ });
90
+
91
+ it("RateLimitError has correct name", () => {
92
+ const error = new RateLimitError("Rate limit exceeded", { statusCode: 429 });
93
+ expect(error.name).toBe("RateLimitError");
94
+ expect(error.statusCode).toBe(429);
95
+ });
96
+
97
+ it("ValidationError has correct name", () => {
98
+ const error = new ValidationError("Invalid phone number", {
99
+ details: { field: "to" },
100
+ });
101
+ expect(error.name).toBe("ValidationError");
102
+ expect(error.details).toEqual({ field: "to" });
103
+ });
104
+
105
+ it("CallError has correct name", () => {
106
+ const error = new CallError("Call failed");
107
+ expect(error.name).toBe("CallError");
108
+ expect(error instanceof PamelaError).toBe(true);
109
+ });
110
+
111
+ it("exceptions can be created with minimal args", () => {
112
+ const error = new PamelaError("Simple error");
113
+ expect(error.message).toBe("Simple error");
114
+ expect(error.errorCode).toBeUndefined();
115
+ expect(error.details).toBeUndefined();
116
+ });
117
+ });
118
+
119
+ describe("Client Initialization", () => {
120
+ it("initializes with API key", () => {
121
+ const client = new PamelaClient({
122
+ apiKey: "pk_live_test",
123
+ });
124
+ expect(client).toBeDefined();
125
+ expect(client.usage).toBeDefined();
126
+ });
127
+
128
+ it("uses default base URL", () => {
129
+ const client = new PamelaClient({
130
+ apiKey: "pk_live_test",
131
+ });
132
+ // Client is created successfully with default URL
133
+ expect(client).toBeDefined();
134
+ });
135
+
136
+ it("accepts custom base URL", () => {
137
+ const client = new PamelaClient({
138
+ apiKey: "pk_live_test",
139
+ baseUrl: "https://custom.api.com",
140
+ });
141
+ expect(client).toBeDefined();
142
+ });
143
+ });
144
+
145
+ describe("Exception Hierarchy", () => {
146
+ it("all exceptions inherit from PamelaError", () => {
147
+ expect(new AuthenticationError("test") instanceof PamelaError).toBe(true);
148
+ expect(new SubscriptionError("test") instanceof PamelaError).toBe(true);
149
+ expect(new RateLimitError("test") instanceof PamelaError).toBe(true);
150
+ expect(new ValidationError("test") instanceof PamelaError).toBe(true);
151
+ expect(new CallError("test") instanceof PamelaError).toBe(true);
152
+ });
153
+
154
+ it("all exceptions inherit from Error", () => {
155
+ expect(new PamelaError("test") instanceof Error).toBe(true);
156
+ expect(new AuthenticationError("test") instanceof Error).toBe(true);
157
+ expect(new SubscriptionError("test") instanceof Error).toBe(true);
158
+ });
159
+ });
160
+
161
+ // =============================================================================
162
+ // Integration Tests (Require PAMELA_TEST_API_KEY)
163
+ // =============================================================================
164
+
14
165
  describe("PamelaClient SDK", () => {
15
166
  it("initializes with API key", () => {
16
167
  const client = new PamelaClient({
17
- apiKey: TEST_API_KEY || "pk_test_placeholder",
168
+ apiKey: TEST_API_KEY || "pk_live_placeholder",
18
169
  baseUrl: TEST_API_URL,
19
170
  });
20
171
  expect(client).toBeDefined();
@@ -42,5 +193,10 @@ describe("PamelaClient SDK", () => {
42
193
  expect(usage).toBeDefined();
43
194
  expect(usage.call_count).toBeDefined();
44
195
  });
196
+
197
+ it("lists tools", async () => {
198
+ const tools = await client.listTools();
199
+ expect(Array.isArray(tools)).toBe(true);
200
+ });
45
201
  });
46
202
 
package/tests/setup.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  // Set test API URL (use staging or mock server)
8
8
  export const TEST_API_URL = process.env.TEST_API_URL || "https://pamela-dev.up.railway.app";
9
9
 
10
- // Test API key (use test keys from staging environment)
11
- export const TEST_API_KEY = process.env.TEST_API_KEY || "pk_test_placeholder";
10
+ // API key for tests (use a staging key)
11
+ export const TEST_API_KEY = process.env.TEST_API_KEY || "pk_live_placeholder";
12
12
 
13
13
  // Mock data helpers
14
14
  export const mockCallResponse = {