@thisispamela/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +134 -0
- package/jest.config.js +13 -0
- package/package.json +33 -0
- package/src/index.ts +189 -0
- package/tests/pamela.test.ts +136 -0
- package/tests/setup.ts +31 -0
- package/tsconfig.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Pamela SDK for JavaScript/TypeScript
|
|
2
|
+
|
|
3
|
+
Official SDK for the Pamela Voice API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @pamela/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Example
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { PamelaClient } from '@pamela/sdk';
|
|
17
|
+
|
|
18
|
+
const client = new PamelaClient({
|
|
19
|
+
apiKey: 'pk_live_your_api_key_here',
|
|
20
|
+
baseUrl: 'https://api.thisispamela.com', // Optional
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Create a call
|
|
24
|
+
const call = await client.createCall({
|
|
25
|
+
to: '+1234567890',
|
|
26
|
+
task: 'Order a large pizza for delivery',
|
|
27
|
+
locale: 'en-US',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('Call created:', call.id);
|
|
31
|
+
|
|
32
|
+
// Get call status
|
|
33
|
+
const status = await client.getCall(call.id);
|
|
34
|
+
console.log('Call status:', status.status);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Webhook Verification
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { PamelaClient } from '@pamela/b2b-sdk';
|
|
41
|
+
import express from 'express';
|
|
42
|
+
|
|
43
|
+
const app = express();
|
|
44
|
+
const WEBHOOK_SECRET = 'your_webhook_secret';
|
|
45
|
+
|
|
46
|
+
app.post('/webhooks/pamela', express.json(), (req, res) => {
|
|
47
|
+
const signature = req.headers['x-pamela-signature'] as string;
|
|
48
|
+
const payload = req.body;
|
|
49
|
+
|
|
50
|
+
if (!PamelaClient.verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
|
|
51
|
+
return res.status(401).send('Invalid signature');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle webhook event
|
|
55
|
+
console.log('Webhook event:', payload.event);
|
|
56
|
+
console.log('Call ID:', payload.call_id);
|
|
57
|
+
|
|
58
|
+
res.status(200).send('OK');
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
See the [Pamela B2B API Documentation](https://docs.thisispamela.com/b2b) for full API reference.
|
|
65
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pamela B2B Voice API SDK for JavaScript/TypeScript
|
|
3
|
+
*/
|
|
4
|
+
export interface PamelaClientConfig {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateCallRequest {
|
|
9
|
+
to: string;
|
|
10
|
+
country?: string;
|
|
11
|
+
locale?: string;
|
|
12
|
+
task: string;
|
|
13
|
+
instructions?: string;
|
|
14
|
+
end_user_id?: string;
|
|
15
|
+
metadata?: Record<string, any>;
|
|
16
|
+
tools?: Array<Record<string, any>>;
|
|
17
|
+
webhooks?: {
|
|
18
|
+
webhook_url?: string;
|
|
19
|
+
tool_webhook_url?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface CallResponse {
|
|
23
|
+
id: string;
|
|
24
|
+
status: string;
|
|
25
|
+
call_session_id: string;
|
|
26
|
+
created_at: string;
|
|
27
|
+
}
|
|
28
|
+
export interface CallStatus {
|
|
29
|
+
id: string;
|
|
30
|
+
status: string;
|
|
31
|
+
to: string;
|
|
32
|
+
from_: string;
|
|
33
|
+
country: string;
|
|
34
|
+
created_at: string;
|
|
35
|
+
started_at?: string;
|
|
36
|
+
completed_at?: string;
|
|
37
|
+
duration_seconds?: number;
|
|
38
|
+
transcript?: Array<Record<string, any>>;
|
|
39
|
+
summary?: string;
|
|
40
|
+
metadata: Record<string, any>;
|
|
41
|
+
end_user_id?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ToolDefinition {
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
input_schema: Record<string, any>;
|
|
47
|
+
output_schema?: Record<string, any>;
|
|
48
|
+
timeout_ms?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface WebhookPayload {
|
|
51
|
+
event: string;
|
|
52
|
+
call_id: string;
|
|
53
|
+
call_session_id: string;
|
|
54
|
+
timestamp: string;
|
|
55
|
+
data: Record<string, any>;
|
|
56
|
+
}
|
|
57
|
+
export declare class PamelaClient {
|
|
58
|
+
private client;
|
|
59
|
+
private apiKey;
|
|
60
|
+
constructor(config: PamelaClientConfig);
|
|
61
|
+
/**
|
|
62
|
+
* Create a new call.
|
|
63
|
+
*/
|
|
64
|
+
createCall(request: CreateCallRequest): Promise<CallResponse>;
|
|
65
|
+
/**
|
|
66
|
+
* Get call status and details.
|
|
67
|
+
*/
|
|
68
|
+
getCall(callId: string): Promise<CallStatus>;
|
|
69
|
+
/**
|
|
70
|
+
* List calls for the authenticated partner/project.
|
|
71
|
+
*/
|
|
72
|
+
listCalls(params?: {
|
|
73
|
+
status?: string;
|
|
74
|
+
limit?: number;
|
|
75
|
+
offset?: number;
|
|
76
|
+
start_date?: string;
|
|
77
|
+
end_date?: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
items: CallStatus[];
|
|
80
|
+
total: number;
|
|
81
|
+
limit: number;
|
|
82
|
+
offset: number;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Cancel an in-progress call.
|
|
86
|
+
*/
|
|
87
|
+
cancelCall(callId: string): Promise<{
|
|
88
|
+
success: boolean;
|
|
89
|
+
call_id: string;
|
|
90
|
+
status: string;
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* Register a tool.
|
|
94
|
+
*/
|
|
95
|
+
registerTool(tool: ToolDefinition): Promise<{
|
|
96
|
+
id: string;
|
|
97
|
+
project_id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
description: string;
|
|
100
|
+
input_schema: Record<string, any>;
|
|
101
|
+
output_schema: Record<string, any>;
|
|
102
|
+
timeout_ms: number;
|
|
103
|
+
created_at: string;
|
|
104
|
+
}>;
|
|
105
|
+
/**
|
|
106
|
+
* List all tools for the project.
|
|
107
|
+
*/
|
|
108
|
+
listTools(): Promise<Array<{
|
|
109
|
+
id: string;
|
|
110
|
+
project_id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
description: string;
|
|
113
|
+
input_schema: Record<string, any>;
|
|
114
|
+
output_schema: Record<string, any>;
|
|
115
|
+
timeout_ms: number;
|
|
116
|
+
created_at: string;
|
|
117
|
+
}>>;
|
|
118
|
+
/**
|
|
119
|
+
* Delete a tool.
|
|
120
|
+
*/
|
|
121
|
+
deleteTool(toolId: string): Promise<{
|
|
122
|
+
success: boolean;
|
|
123
|
+
tool_id: string;
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* Verify webhook signature.
|
|
127
|
+
*/
|
|
128
|
+
static verifyWebhookSignature(payload: string | object, signature: string, secret: string): boolean;
|
|
129
|
+
}
|
|
130
|
+
export default PamelaClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pamela B2B Voice API SDK for JavaScript/TypeScript
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.PamelaClient = void 0;
|
|
43
|
+
const axios_1 = __importDefault(require("axios"));
|
|
44
|
+
const crypto = __importStar(require("crypto"));
|
|
45
|
+
class PamelaClient {
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.apiKey = config.apiKey;
|
|
48
|
+
const baseURL = config.baseUrl || 'https://api.thisispamela.com';
|
|
49
|
+
this.client = axios_1.default.create({
|
|
50
|
+
baseURL: `${baseURL}/api/b2b/v1`,
|
|
51
|
+
headers: {
|
|
52
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
},
|
|
55
|
+
timeout: 30000,
|
|
56
|
+
});
|
|
57
|
+
// Add retry logic
|
|
58
|
+
this.client.interceptors.response.use((response) => response, async (error) => {
|
|
59
|
+
const config = error.config;
|
|
60
|
+
if (!config || !config.retry) {
|
|
61
|
+
config.retry = 0;
|
|
62
|
+
}
|
|
63
|
+
config.retry += 1;
|
|
64
|
+
if (config.retry <= 3 && error.response?.status && error.response.status >= 500) {
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * config.retry));
|
|
66
|
+
return this.client.request(config);
|
|
67
|
+
}
|
|
68
|
+
return Promise.reject(error);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a new call.
|
|
73
|
+
*/
|
|
74
|
+
async createCall(request) {
|
|
75
|
+
const response = await this.client.post('/calls', request);
|
|
76
|
+
return response.data;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get call status and details.
|
|
80
|
+
*/
|
|
81
|
+
async getCall(callId) {
|
|
82
|
+
const response = await this.client.get(`/calls/${callId}`);
|
|
83
|
+
return response.data;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* List calls for the authenticated partner/project.
|
|
87
|
+
*/
|
|
88
|
+
async listCalls(params) {
|
|
89
|
+
const response = await this.client.get('/calls', { params });
|
|
90
|
+
// Backend returns { items: [...], total, limit, offset }
|
|
91
|
+
return response.data;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Cancel an in-progress call.
|
|
95
|
+
*/
|
|
96
|
+
async cancelCall(callId) {
|
|
97
|
+
const response = await this.client.post(`/calls/${callId}/cancel`);
|
|
98
|
+
return response.data;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Register a tool.
|
|
102
|
+
*/
|
|
103
|
+
async registerTool(tool) {
|
|
104
|
+
const response = await this.client.post('/tools', { tool });
|
|
105
|
+
return response.data;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* List all tools for the project.
|
|
109
|
+
*/
|
|
110
|
+
async listTools() {
|
|
111
|
+
const response = await this.client.get('/tools');
|
|
112
|
+
return response.data;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Delete a tool.
|
|
116
|
+
*/
|
|
117
|
+
async deleteTool(toolId) {
|
|
118
|
+
const response = await this.client.delete(`/tools/${toolId}`);
|
|
119
|
+
return response.data;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Verify webhook signature.
|
|
123
|
+
*/
|
|
124
|
+
static verifyWebhookSignature(payload, signature, secret) {
|
|
125
|
+
const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
126
|
+
const expectedSignature = crypto
|
|
127
|
+
.createHmac('sha256', secret)
|
|
128
|
+
.update(payloadString)
|
|
129
|
+
.digest('hex');
|
|
130
|
+
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.PamelaClient = PamelaClient;
|
|
134
|
+
exports.default = PamelaClient;
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/tests'],
|
|
5
|
+
testMatch: ['**/*.test.ts'],
|
|
6
|
+
collectCoverageFrom: [
|
|
7
|
+
'src/**/*.ts',
|
|
8
|
+
'!src/**/*.d.ts',
|
|
9
|
+
],
|
|
10
|
+
coverageDirectory: 'coverage',
|
|
11
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
12
|
+
};
|
|
13
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thisispamela/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pamela B2B Voice API SDK for JavaScript/TypeScript",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"test:watch": "jest --watch",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pamela",
|
|
15
|
+
"voice",
|
|
16
|
+
"api",
|
|
17
|
+
"b2b",
|
|
18
|
+
"phone",
|
|
19
|
+
"calling"
|
|
20
|
+
],
|
|
21
|
+
"author": "Pamela",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"axios": "^1.6.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"@types/jest": "^29.5.0",
|
|
29
|
+
"typescript": "^5.0.0",
|
|
30
|
+
"jest": "^29.7.0",
|
|
31
|
+
"ts-jest": "^29.1.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pamela B2B Voice API SDK for JavaScript/TypeScript
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
6
|
+
import * as crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
export interface PamelaClientConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreateCallRequest {
|
|
14
|
+
to: string;
|
|
15
|
+
country?: string;
|
|
16
|
+
locale?: string;
|
|
17
|
+
task: string;
|
|
18
|
+
instructions?: string;
|
|
19
|
+
end_user_id?: string;
|
|
20
|
+
metadata?: Record<string, any>;
|
|
21
|
+
tools?: Array<Record<string, any>>;
|
|
22
|
+
webhooks?: {
|
|
23
|
+
webhook_url?: string;
|
|
24
|
+
tool_webhook_url?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CallResponse {
|
|
29
|
+
id: string;
|
|
30
|
+
status: string;
|
|
31
|
+
call_session_id: string;
|
|
32
|
+
created_at: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CallStatus {
|
|
36
|
+
id: string;
|
|
37
|
+
status: string;
|
|
38
|
+
to: string;
|
|
39
|
+
from_: string; // Backend uses from_ (Python keyword)
|
|
40
|
+
country: string;
|
|
41
|
+
created_at: string;
|
|
42
|
+
started_at?: string;
|
|
43
|
+
completed_at?: string;
|
|
44
|
+
duration_seconds?: number;
|
|
45
|
+
transcript?: Array<Record<string, any>>;
|
|
46
|
+
summary?: string;
|
|
47
|
+
metadata: Record<string, any>;
|
|
48
|
+
end_user_id?: string; // Marketplace end-user ID for privacy isolation
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ToolDefinition {
|
|
52
|
+
name: string;
|
|
53
|
+
description: string;
|
|
54
|
+
input_schema: Record<string, any>;
|
|
55
|
+
output_schema?: Record<string, any>;
|
|
56
|
+
timeout_ms?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface WebhookPayload {
|
|
60
|
+
event: string;
|
|
61
|
+
call_id: string;
|
|
62
|
+
call_session_id: string;
|
|
63
|
+
timestamp: string;
|
|
64
|
+
data: Record<string, any>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class PamelaClient {
|
|
68
|
+
private client: AxiosInstance;
|
|
69
|
+
private apiKey: string;
|
|
70
|
+
|
|
71
|
+
constructor(config: PamelaClientConfig) {
|
|
72
|
+
this.apiKey = config.apiKey;
|
|
73
|
+
const baseURL = config.baseUrl || 'https://api.thisispamela.com';
|
|
74
|
+
|
|
75
|
+
this.client = axios.create({
|
|
76
|
+
baseURL: `${baseURL}/api/b2b/v1`,
|
|
77
|
+
headers: {
|
|
78
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
},
|
|
81
|
+
timeout: 30000,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Add retry logic
|
|
85
|
+
this.client.interceptors.response.use(
|
|
86
|
+
(response) => response,
|
|
87
|
+
async (error: AxiosError) => {
|
|
88
|
+
const config = error.config as any;
|
|
89
|
+
if (!config || !config.retry) {
|
|
90
|
+
config.retry = 0;
|
|
91
|
+
}
|
|
92
|
+
config.retry += 1;
|
|
93
|
+
|
|
94
|
+
if (config.retry <= 3 && error.response?.status && error.response.status >= 500) {
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * config.retry));
|
|
96
|
+
return this.client.request(config);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Promise.reject(error);
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create a new call.
|
|
106
|
+
*/
|
|
107
|
+
async createCall(request: CreateCallRequest): Promise<CallResponse> {
|
|
108
|
+
const response = await this.client.post<CallResponse>('/calls', request);
|
|
109
|
+
return response.data;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get call status and details.
|
|
114
|
+
*/
|
|
115
|
+
async getCall(callId: string): Promise<CallStatus> {
|
|
116
|
+
const response = await this.client.get<CallStatus>(`/calls/${callId}`);
|
|
117
|
+
return response.data;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* List calls for the authenticated partner/project.
|
|
122
|
+
*/
|
|
123
|
+
async listCalls(params?: {
|
|
124
|
+
status?: string;
|
|
125
|
+
limit?: number;
|
|
126
|
+
offset?: number;
|
|
127
|
+
start_date?: string;
|
|
128
|
+
end_date?: string;
|
|
129
|
+
}): Promise<{ items: CallStatus[]; total: number; limit: number; offset: number }> {
|
|
130
|
+
const response = await this.client.get('/calls', { params });
|
|
131
|
+
// Backend returns { items: [...], total, limit, offset }
|
|
132
|
+
return response.data;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cancel an in-progress call.
|
|
137
|
+
*/
|
|
138
|
+
async cancelCall(callId: string): Promise<{ success: boolean; call_id: string; status: string }> {
|
|
139
|
+
const response = await this.client.post(`/calls/${callId}/cancel`);
|
|
140
|
+
return response.data;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Register a tool.
|
|
145
|
+
*/
|
|
146
|
+
async registerTool(tool: ToolDefinition): Promise<{ id: string; project_id: string; name: string; description: string; input_schema: Record<string, any>; output_schema: Record<string, any>; timeout_ms: number; created_at: string }> {
|
|
147
|
+
const response = await this.client.post('/tools', { tool });
|
|
148
|
+
return response.data;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* List all tools for the project.
|
|
153
|
+
*/
|
|
154
|
+
async listTools(): Promise<Array<{ id: string; project_id: string; name: string; description: string; input_schema: Record<string, any>; output_schema: Record<string, any>; timeout_ms: number; created_at: string }>> {
|
|
155
|
+
const response = await this.client.get('/tools');
|
|
156
|
+
return response.data;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Delete a tool.
|
|
161
|
+
*/
|
|
162
|
+
async deleteTool(toolId: string): Promise<{ success: boolean; tool_id: string }> {
|
|
163
|
+
const response = await this.client.delete(`/tools/${toolId}`);
|
|
164
|
+
return response.data;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Verify webhook signature.
|
|
169
|
+
*/
|
|
170
|
+
static verifyWebhookSignature(
|
|
171
|
+
payload: string | object,
|
|
172
|
+
signature: string,
|
|
173
|
+
secret: string
|
|
174
|
+
): boolean {
|
|
175
|
+
const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
176
|
+
const expectedSignature = crypto
|
|
177
|
+
.createHmac('sha256', secret)
|
|
178
|
+
.update(payloadString)
|
|
179
|
+
.digest('hex');
|
|
180
|
+
|
|
181
|
+
return crypto.timingSafeEqual(
|
|
182
|
+
Buffer.from(signature),
|
|
183
|
+
Buffer.from(expectedSignature)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export default PamelaClient;
|
|
189
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for Pamela JavaScript SDK.
|
|
3
|
+
*
|
|
4
|
+
* Tests against staging API or mocked responses.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Pamela } from "../src/index";
|
|
8
|
+
|
|
9
|
+
const TEST_API_URL = process.env.TEST_API_URL || "https://pamela-dev.up.railway.app";
|
|
10
|
+
const TEST_API_KEY = process.env.TEST_API_KEY || "pk_test_placeholder";
|
|
11
|
+
|
|
12
|
+
describe("Pamela SDK", () => {
|
|
13
|
+
let sdk: Pamela;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
sdk = new Pamela({
|
|
17
|
+
apiKey: TEST_API_KEY,
|
|
18
|
+
apiUrl: TEST_API_URL,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("Initialization", () => {
|
|
23
|
+
it("should initialize with API key", () => {
|
|
24
|
+
expect(sdk).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should throw error without API key", () => {
|
|
28
|
+
expect(() => {
|
|
29
|
+
new Pamela({ apiKey: "" });
|
|
30
|
+
}).toThrow();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("Call Creation", () => {
|
|
35
|
+
it("should create a call with required parameters", async () => {
|
|
36
|
+
// Mock or use staging API
|
|
37
|
+
const call = await sdk.calls.create({
|
|
38
|
+
to: "+1234567890",
|
|
39
|
+
from_: "+1987654321",
|
|
40
|
+
country: "US",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(call).toBeDefined();
|
|
44
|
+
expect(call.id).toBeDefined();
|
|
45
|
+
expect(call.status).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle invalid phone numbers", async () => {
|
|
49
|
+
await expect(
|
|
50
|
+
sdk.calls.create({
|
|
51
|
+
to: "invalid",
|
|
52
|
+
from_: "+1987654321",
|
|
53
|
+
country: "US",
|
|
54
|
+
})
|
|
55
|
+
).rejects.toThrow();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("Call Status", () => {
|
|
60
|
+
it("should get call status by ID", async () => {
|
|
61
|
+
const callId = "test_call_id";
|
|
62
|
+
const status = await sdk.calls.getStatus(callId);
|
|
63
|
+
|
|
64
|
+
expect(status).toBeDefined();
|
|
65
|
+
expect(status.id).toBe(callId);
|
|
66
|
+
expect(status.status).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should handle non-existent call ID", async () => {
|
|
70
|
+
await expect(sdk.calls.getStatus("nonexistent")).rejects.toThrow();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("Call Cancellation", () => {
|
|
75
|
+
it("should cancel an in-progress call", async () => {
|
|
76
|
+
const callId = "test_call_id";
|
|
77
|
+
const result = await sdk.calls.cancel(callId);
|
|
78
|
+
|
|
79
|
+
expect(result).toBeDefined();
|
|
80
|
+
expect(result.success).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle cancelling already completed call", async () => {
|
|
84
|
+
await expect(sdk.calls.cancel("completed_call")).rejects.toThrow();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("Usage", () => {
|
|
89
|
+
it("should get usage statistics", async () => {
|
|
90
|
+
const usage = await sdk.usage.get("2024-01");
|
|
91
|
+
|
|
92
|
+
expect(usage).toBeDefined();
|
|
93
|
+
expect(usage.call_count).toBeDefined();
|
|
94
|
+
expect(usage.quota).toBeDefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should handle invalid period format", async () => {
|
|
98
|
+
await expect(sdk.usage.get("invalid")).rejects.toThrow();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("Error Handling", () => {
|
|
103
|
+
it("should handle network errors", async () => {
|
|
104
|
+
// Test with invalid API URL
|
|
105
|
+
const badSdk = new Pamela({
|
|
106
|
+
apiKey: TEST_API_KEY,
|
|
107
|
+
apiUrl: "https://invalid-url.example.com",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await expect(
|
|
111
|
+
badSdk.calls.create({
|
|
112
|
+
to: "+1234567890",
|
|
113
|
+
from_: "+1987654321",
|
|
114
|
+
country: "US",
|
|
115
|
+
})
|
|
116
|
+
).rejects.toThrow();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle API errors (400, 401, 403, 500)", async () => {
|
|
120
|
+
// Test with invalid API key
|
|
121
|
+
const badSdk = new Pamela({
|
|
122
|
+
apiKey: "invalid_key",
|
|
123
|
+
apiUrl: TEST_API_URL,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await expect(
|
|
127
|
+
badSdk.calls.create({
|
|
128
|
+
to: "+1234567890",
|
|
129
|
+
from_: "+1987654321",
|
|
130
|
+
country: "US",
|
|
131
|
+
})
|
|
132
|
+
).rejects.toThrow();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test setup for JavaScript SDK tests.
|
|
3
|
+
*
|
|
4
|
+
* Configure test environment, mock API responses, etc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Set test API URL (use staging or mock server)
|
|
8
|
+
export const TEST_API_URL = process.env.TEST_API_URL || "https://pamela-dev.up.railway.app";
|
|
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";
|
|
12
|
+
|
|
13
|
+
// Mock data helpers
|
|
14
|
+
export const mockCallResponse = {
|
|
15
|
+
id: "call_test_123",
|
|
16
|
+
status: "queued",
|
|
17
|
+
to: "+1234567890",
|
|
18
|
+
from_: "+1987654321",
|
|
19
|
+
created_at: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const mockCallStatusResponse = {
|
|
23
|
+
id: "call_test_123",
|
|
24
|
+
status: "completed",
|
|
25
|
+
to: "+1234567890",
|
|
26
|
+
from_: "+1987654321",
|
|
27
|
+
duration_seconds: 120,
|
|
28
|
+
created_at: new Date().toISOString(),
|
|
29
|
+
completed_at: new Date().toISOString(),
|
|
30
|
+
};
|
|
31
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
18
|
+
|