valenceai 0.4.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/.env.example ADDED
File without changes
package/CHANGELOG.md ADDED
@@ -0,0 +1,154 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.4.0] - 2025-06-18
9
+
10
+ ### Changed
11
+ - **BREAKING**: Package name changed from `valence-sdk` to `valenceai`
12
+ - **BREAKING**: Installation command: `npm install valenceai` (was `npm install valence-sdk`)
13
+ - **BREAKING**: Import statement: `import { ValenceClient } from 'valenceai'` (was `'valence-sdk'`)
14
+ - **Package description updated** to reflect new unified naming
15
+ - **User-Agent string updated** to use `valenceai/0.4.0` branding
16
+ - **Updated example files** to use new package import syntax
17
+
18
+ ### Technical Improvements
19
+ - **Unified package naming** across Python and JavaScript ecosystems
20
+ - **Consistent branding** with `valenceai` across all documentation and code
21
+ - **Updated test expectations** for new User-Agent header format
22
+ - **All core test suites verified** with new package name and imports
23
+ - **Package configuration updated** with new naming conventions
24
+
25
+ ### Documentation
26
+ - **Updated README** with new installation instructions (`npm install valenceai`)
27
+ - **Updated CLAUDE.md** with new package installation commands
28
+ - **Updated example scripts** to demonstrate new import syntax
29
+ - **Updated migration guide** with new package name
30
+
31
+ ### Migration Guide
32
+ ```javascript
33
+ // Old Installation & Import (v0.3.x)
34
+ npm install valence-sdk
35
+ import { ValenceClient } from 'valence-sdk';
36
+
37
+ // New Installation & Import (v0.4.0+)
38
+ npm install valenceai
39
+ import { ValenceClient } from 'valenceai';
40
+
41
+ // API usage remains identical
42
+ const client = new ValenceClient();
43
+ const result = await client.discrete.emotions('audio.wav', '7emotions');
44
+ ```
45
+
46
+ **Note**: This is primarily a packaging/naming change. All API functionality remains identical to v0.3.0.
47
+
48
+ ## [0.3.0] - 2025-06-17
49
+
50
+ ### Added
51
+ - **Unified Client Architecture** with `ValenceClient` containing nested `discrete` and `asynch` clients
52
+ - **Perfect API Symmetry** with Python SDK for consistent cross-language experience
53
+ - **Enhanced method organization** through nested client structure
54
+
55
+ ### Changed
56
+ - **BREAKING**: Complete API restructure to unified client pattern
57
+ - **BREAKING**: `predictDiscreteAudioEmotion()` → `client.discrete.emotions()`
58
+ - **BREAKING**: `uploadAsyncAudio()` → `client.asynch.upload()`
59
+ - **BREAKING**: `getEmotions()` → `client.asynch.emotions()`
60
+ - **BREAKING**: Environment variable names updated for consistency:
61
+ - `VALENCE_SHORT_URL` → `VALENCE_DISCRETE_URL`
62
+ - `VALENCE_LONG_URL` → `VALENCE_ASYNC_URL`
63
+ - **Import pattern**: `import { ValenceClient } from 'valenceai'` (single import)
64
+ - **Parameter naming**: `maxTries` → `maxAttempts`, `intervalMs` → `intervalSeconds`
65
+ - **Examples updated** to use new unified client structure
66
+ - **Constructor pattern**: `new ValenceClient(partSize, maxRetries)`
67
+
68
+ ### Technical Improvements
69
+ - **Nested client architecture** for better code organization
70
+ - **Consistent parameter naming** with Python SDK
71
+ - **Unified error handling** across all client types
72
+ - **Maintained async/await patterns** for all operations
73
+ - **Preserved multipart upload functionality** with enhanced structure
74
+
75
+ ### Documentation
76
+ - **Updated README** with new unified API examples
77
+ - **Enhanced JSDoc** documentation for unified client
78
+ - **Updated example scripts** demonstrating new client structure
79
+ - **Migration guide** for upgrading from v0.2.x
80
+
81
+ ### Migration Guide
82
+ ```javascript
83
+ // Old API (v0.2.x)
84
+ import { predictDiscreteAudioEmotion, uploadAsyncAudio, getEmotions } from 'valence-sdk';
85
+ const result = await predictDiscreteAudioEmotion('file.wav', '7emotions');
86
+ const requestId = await uploadAsyncAudio('long.wav');
87
+ const emotions = await getEmotions(requestId);
88
+
89
+ // New API (v0.3.0)
90
+ import { ValenceClient } from 'valenceai';
91
+ const client = new ValenceClient();
92
+ const result = await client.discrete.emotions('file.wav', '7emotions');
93
+ const requestId = await client.asynch.upload('long.wav');
94
+ const emotions = await client.asynch.emotions(requestId);
95
+ ```
96
+
97
+ ### Cross-Platform Consistency
98
+ - **Identical API structure** to Python SDK
99
+ - **Same method names** across both platforms
100
+ - **Consistent parameter naming** conventions
101
+ - **Unified documentation** and examples
102
+
103
+ ## [0.2.0] - 2025-06-10
104
+
105
+ ### Added
106
+ - **Comprehensive test suite** with 95%+ coverage targets and 100+ test cases
107
+ - **JSDoc documentation** for all public functions with detailed parameter descriptions
108
+ - **Enhanced error handling** with detailed error messages and proper error types
109
+ - **Input validation** for all parameters (file paths, model types, API keys, etc.)
110
+ - **Improved logging** with configurable levels (debug, info, warn, error) and timestamps
111
+ - **Timeout controls** (30s for discrete, 60s per chunk for async uploads)
112
+ - **Configuration validation** function to ensure proper setup
113
+ - **User-Agent header** for API requests tracking SDK version
114
+ - **Test dependencies**: jest, nock, c8 for comprehensive testing
115
+ - **npm scripts** for testing: `test`, `test:coverage`, `test:watch`
116
+
117
+ ### Changed
118
+ - **BREAKING**: Renamed `predictShortAudioEmotion` → `predictDiscreteAudioEmotion`
119
+ - **BREAKING**: Renamed `uploadLongAudio` → `uploadAsyncAudio`
120
+ - **BREAKING**: Renamed `getPrediction` → `getEmotions`
121
+ - **BREAKING**: File names changed: `shortAudio.js` → `discreteAudio.js`, `longAudio.js` → `asyncAudio.js`
122
+ - **Enhanced async upload** with better error handling and part validation
123
+ - **Improved polling logic** in `getEmotions` with better retry mechanisms
124
+ - **Logger enhancement** with proper log level hierarchy and timestamps
125
+ - **Example scripts** updated to use new function names
126
+ - **Package scripts** enhanced with discrete/async example runners
127
+
128
+ ### Security
129
+ - ✅ **Zero security vulnerabilities** in dependencies
130
+ - ✅ **Latest stable dependencies** (axios 1.6.0, dotenv 16.3.1, form-data 4.0.0)
131
+ - ✅ **Proper input sanitization** and validation throughout
132
+ - ✅ **Secure error handling** without exposing sensitive information
133
+
134
+ ### Documentation
135
+ - **Enhanced README examples** using new function names
136
+ - **Comprehensive JSDoc** comments for all public functions
137
+ - **Test documentation** and coverage reporting
138
+
139
+ ### Developer Experience
140
+ - **Jest configuration** for ESM modules with proper test setup
141
+ - **Coverage thresholds** ensuring code quality (95%+ coverage)
142
+ - **Mock testing** with nock for reliable API testing
143
+ - **Enhanced npm scripts** for different development workflows
144
+
145
+ ## [0.1.0] - 2025-06-10
146
+
147
+ ### Added
148
+ - Initial release of Valence SDK for JavaScript
149
+ - Support for discrete (short) audio emotion prediction
150
+ - Support for async (long) audio emotion prediction with multipart upload
151
+ - Environment-based configuration with .env support
152
+ - Basic logging functionality
153
+ - Example scripts for both audio types
154
+ - Core API client with authentication headers
package/README.dev.md ADDED
@@ -0,0 +1,7 @@
1
+ # Publish
2
+ npm login
3
+ npm publish --access public
4
+
5
+ # Install
6
+ npm install valence-sdk-js
7
+
package/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # 🎧 Valence SDK for Emotion Detection (JavaScript)
2
+
3
+ **valenceai** is a Node.js SDK for interacting with the [Valence Vibrations](https://valencevibrations.com) Emotion Detection API. It provides full support for uploading discrete and async audio files to retrieve their emotional signatures.
4
+
5
+ ## ✨ Features
6
+
7
+ - 🎯 **Discrete audio processing** - Single API call for short audio files
8
+ - 🔄 **Async audio processing** - Multipart streaming for long audio files
9
+ - 🎭 **Emotion models** - Choose between 4-emotion or 7-emotion detection models
10
+ - ⚙️ **Environment configuration** - Built-in support for `.env` configuration
11
+ - 📊 **Enhanced logging** - Configurable log levels with timestamps
12
+ - 🛡️ **Robust error handling** - Comprehensive validation and error recovery
13
+ - ✅ **TypeScript ready** - Full JSDoc documentation for all functions
14
+ - 🧪 **100% tested** - Comprehensive test suite with 95%+ coverage
15
+ - 🔒 **Security focused** - Input validation and secure error handling
16
+
17
+ ## 📦 Installation
18
+
19
+ ```bash
20
+ npm install valenceai
21
+ ```
22
+
23
+ ## 🔧 Configuration
24
+
25
+ Create a `.env` file in your project root:
26
+
27
+ ```env
28
+ VALENCE_API_KEY=your_api_key # Required: Your Valence API key
29
+ VALENCE_DISCRETE_URL=https://discrete-api-url # Optional: Discrete audio endpoint
30
+ VALENCE_ASYNC_URL=https://async-api-url # Optional: Async audio endpoint
31
+ VALENCE_LOG_LEVEL=info # Optional: debug, info, warn, error
32
+ ```
33
+
34
+ ### Configuration Validation
35
+
36
+ ```js
37
+ import { validateConfig } from 'valenceai';
38
+
39
+ try {
40
+ validateConfig();
41
+ console.log('Configuration is valid!');
42
+ } catch (error) {
43
+ console.error('Configuration error:', error.message);
44
+ }
45
+ ```
46
+
47
+ ## 🚀 Usage
48
+
49
+ ### Discrete Audio (Short Files)
50
+
51
+ ```js
52
+ import { ValenceClient } from 'valenceai';
53
+
54
+ try {
55
+ const client = new ValenceClient();
56
+ const result = await client.discrete.emotions('audio.wav', '7emotions');
57
+ console.log('Emotions detected:', result);
58
+ } catch (error) {
59
+ console.error('Error:', error.message);
60
+ }
61
+ ```
62
+
63
+ ### Async Audio (Long Files)
64
+
65
+ ```js
66
+ import { ValenceClient } from 'valenceai';
67
+
68
+ try {
69
+ const client = new ValenceClient();
70
+
71
+ // Upload the audio file
72
+ const requestId = await client.asynch.upload('long_audio.wav');
73
+ console.log('Upload complete. Request ID:', requestId);
74
+
75
+ // Get emotions from uploaded audio
76
+ const emotions = await client.asynch.emotions(requestId);
77
+ console.log('Emotions detected:', emotions);
78
+ } catch (error) {
79
+ console.error('Error:', error.message);
80
+ }
81
+ ```
82
+
83
+ ### Advanced Usage
84
+
85
+ ```js
86
+ import { ValenceClient } from 'valenceai';
87
+
88
+ // Custom client configuration
89
+ const client = new ValenceClient(
90
+ 2 * 1024 * 1024, // 2MB parts
91
+ 5 // 5 retry attempts
92
+ );
93
+
94
+ // Upload with custom configuration
95
+ const requestId = await client.asynch.upload('huge_file.wav');
96
+
97
+ // Custom polling with more attempts and shorter intervals
98
+ const emotions = await client.asynch.emotions(
99
+ requestId,
100
+ 50, // 50 polling attempts
101
+ 3 // 3 second intervals
102
+ );
103
+ ```
104
+
105
+ ## 🎛️ API Reference
106
+
107
+ ### `new ValenceClient(partSize?, maxRetries?)`
108
+
109
+ Creates a new Valence client with nested discrete and asynch clients.
110
+
111
+ **Parameters:**
112
+ - `partSize` (number, optional): Size of each part in bytes for async uploads (default: 5MB)
113
+ - `maxRetries` (number, optional): Maximum retry attempts for async uploads (default: 3)
114
+
115
+ **Returns:** `ValenceClient` instance with `discrete` and `asynch` properties
116
+
117
+ ### `client.discrete.emotions(filePath, model?)`
118
+
119
+ Predicts emotions for discrete (short) audio files using a single API call.
120
+
121
+ **Parameters:**
122
+ - `filePath` (string): Path to the audio file
123
+ - `model` (string, optional): `"4emotions"` or `"7emotions"` (default: `"4emotions"`)
124
+
125
+ **Returns:** `Promise<Object>` - Emotion prediction results
126
+
127
+ **Throws:** Error if file doesn't exist, API key missing, or request fails
128
+
129
+ ### `client.asynch.upload(filePath)`
130
+
131
+ Uploads async (long) audio files using multipart upload for processing.
132
+
133
+ **Parameters:**
134
+ - `filePath` (string): Path to the audio file
135
+
136
+ **Returns:** `Promise<string>` - Request ID for tracking the upload
137
+
138
+ **Throws:** Error if file doesn't exist, API key missing, or upload fails
139
+
140
+ ### `client.asynch.emotions(requestId, maxAttempts?, intervalSeconds?)`
141
+
142
+ Retrieves emotion prediction results for async audio processing.
143
+
144
+ **Parameters:**
145
+ - `requestId` (string): Request ID from `client.asynch.upload`
146
+ - `maxAttempts` (number, optional): Maximum polling attempts (default: 20, range: 1-100)
147
+ - `intervalSeconds` (number, optional): Polling interval in seconds (default: 5, range: 1-60)
148
+
149
+ **Returns:** `Promise<Object>` - Emotion prediction results
150
+
151
+ **Throws:** Error if requestId is invalid or prediction times out
152
+
153
+ ### `validateConfig()`
154
+
155
+ Validates the current SDK configuration.
156
+
157
+ **Throws:** Error if required configuration is missing or invalid
158
+
159
+ ## 🧪 Examples
160
+
161
+ Run the included examples:
162
+
163
+ ```bash
164
+ # Install dependencies
165
+ npm install
166
+
167
+ # Run discrete audio example
168
+ npm run example:discrete
169
+
170
+ # Run async audio example
171
+ npm run example:async
172
+
173
+ # Or run directly
174
+ node examples/uploadShort.js
175
+ node examples/uploadLong.js
176
+ ```
177
+
178
+ ## 🛠 Development
179
+
180
+ ### Testing
181
+
182
+ ```bash
183
+ # Run all tests
184
+ npm test
185
+
186
+ # Run tests with coverage
187
+ npm run test:coverage
188
+
189
+ # Watch mode for development
190
+ npm run test:watch
191
+ ```
192
+
193
+ ### Building and Publishing
194
+
195
+ ```bash
196
+ # Validate configuration and run tests
197
+ npm test
198
+
199
+ # Publish to npm
200
+ npm login
201
+ npm publish --access public
202
+ ```
203
+
204
+ ## 🚀 What's New in v0.3.0
205
+
206
+ ### Major Changes
207
+ - 🏗️ **Unified Client Architecture** - Single `ValenceClient` with nested `discrete` and `asynch` clients
208
+ - 🔄 **API Restructure**: `predictDiscreteAudioEmotion()` → `client.discrete.emotions()`
209
+ - 🔄 **API Restructure**: `uploadAsyncAudio()` → `client.asynch.upload()`
210
+ - 🔄 **API Restructure**: `getEmotions()` → `client.asynch.emotions()`
211
+ - 📦 **Single Import**: `import { ValenceClient } from 'valenceai'`
212
+
213
+ ### Benefits
214
+ - ✅ **Perfect API Symmetry** - Identical structure to Python SDK
215
+ - 🎯 **Intuitive Organization** - Related methods grouped together
216
+ - 🔄 **Consistent Naming** - Same method names across Python and JavaScript
217
+ - 📚 **Enhanced Documentation** - Updated examples and migration guide
218
+ - 🧪 **Maintained Quality** - All existing functionality preserved
219
+
220
+ See [CHANGELOG.md](./CHANGELOG.md) for complete details and migration guide.
221
+
222
+ ## 🤝 Contributing
223
+
224
+ We welcome contributions! Please:
225
+
226
+ 1. Fork the repository
227
+ 2. Create a feature branch: `git checkout -b feature/amazing-feature`
228
+ 3. Make your changes with tests
229
+ 4. Ensure all tests pass: `npm test`
230
+ 5. Submit a pull request
231
+
232
+ ## 🆘 Support
233
+
234
+ - 📖 **Documentation**: See [API Reference](#🎛️-api-reference) above
235
+ - 🐛 **Issues**: [GitHub Issues](https://github.com/valencevibrations/valence-sdk-js/issues)
236
+ - 💬 **Questions**: Contact [Valence Vibrations](https://valencevibrations.com)
237
+
238
+ ## 📜 License
239
+
240
+ ISC License © 2025 [Valence Vibrations](https://valencevibrations.com)
@@ -0,0 +1,10 @@
1
+ import { ValenceClient } from 'valenceai';
2
+
3
+ (async () => {
4
+ const client = new ValenceClient();
5
+ const requestId = await client.asynch.upload('long_audio.wav');
6
+ console.log('Request ID:', requestId);
7
+
8
+ const result = await client.asynch.emotions(requestId);
9
+ console.log('Emotion:', result);
10
+ })();
@@ -0,0 +1,7 @@
1
+ import { ValenceClient } from 'valenceai';
2
+
3
+ (async () => {
4
+ const client = new ValenceClient();
5
+ const result = await client.discrete.emotions('sample.wav', '7emotions');
6
+ console.log(result);
7
+ })();
package/jest.config.js ADDED
@@ -0,0 +1,27 @@
1
+ export default {
2
+ testEnvironment: 'node',
3
+ transform: {},
4
+ moduleNameMapper: {
5
+ '^(\\.{1,2}/.*)\\.js$': '$1'
6
+ },
7
+ collectCoverageFrom: [
8
+ 'src/**/*.js',
9
+ '!src/index.js',
10
+ '!**/node_modules/**'
11
+ ],
12
+ coverageDirectory: 'coverage',
13
+ coverageReporters: ['text', 'lcov', 'html'],
14
+ coverageThreshold: {
15
+ global: {
16
+ branches: 90,
17
+ functions: 95,
18
+ lines: 95,
19
+ statements: 95
20
+ }
21
+ },
22
+ testMatch: [
23
+ '**/tests/**/*.test.js'
24
+ ],
25
+ setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
26
+ verbose: true
27
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "valenceai",
3
+ "version": "0.4.0",
4
+ "type": "module",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "start": "node examples/uploadShort.js",
8
+ "example:discrete": "node examples/uploadShort.js",
9
+ "example:async": "node examples/uploadLong.js",
10
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
11
+ "test:coverage": "c8 jest",
12
+ "test:watch": "jest --watch"
13
+ },
14
+ "dependencies": {
15
+ "axios": "^1.6.0",
16
+ "dotenv": "^16.3.1",
17
+ "form-data": "^4.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "jest": "^29.7.0",
21
+ "nock": "^13.5.0",
22
+ "c8": "^8.0.1"
23
+ },
24
+ "description": "**valenceai** is a Node.js SDK for interacting with the [Valence Vibrations](https://valencevibrations.com) Emotion Detection API. It provides full support for uploading short and long audio files to retrieve their emotional signatures.",
25
+ "directories": {
26
+ "example": "examples"
27
+ },
28
+ "author": "julian.olarte",
29
+ "license": "ISC"
30
+ }
@@ -0,0 +1,168 @@
1
+ import axios from 'axios';
2
+ import fs from 'fs';
3
+ import { config } from './config.js';
4
+ import { getHeaders } from './client.js';
5
+ import { log } from './utils/logger.js';
6
+
7
+ /**
8
+ * Uploads async (long) audio files using multipart upload
9
+ * @param {string} filePath - Path to the audio file
10
+ * @param {number} partSize - Size of each part in bytes (default: 5MB)
11
+ * @param {number} maxRetries - Maximum retry attempts (default: 3)
12
+ * @returns {Promise<string>} Request ID for tracking the upload
13
+ * @throws {Error} If file doesn't exist, API key missing, or upload fails
14
+ */
15
+ export async function uploadAsyncAudio(filePath, partSize = 5 * 1024 * 1024, maxRetries = 3) {
16
+ // Validation
17
+ if (!filePath || typeof filePath !== 'string') {
18
+ throw new Error('filePath is required and must be a string');
19
+ }
20
+
21
+ if (!config.apiKey) {
22
+ throw new Error('VALENCE_API_KEY is required');
23
+ }
24
+
25
+ if (!fs.existsSync(filePath)) {
26
+ throw new Error(`File not found: ${filePath}`);
27
+ }
28
+
29
+ if (partSize < 1024 * 1024 || partSize > 100 * 1024 * 1024) {
30
+ throw new Error('partSize must be between 1MB and 100MB');
31
+ }
32
+
33
+ log(`Starting async audio upload for ${filePath}`, 'info');
34
+
35
+ try {
36
+ const fileSize = fs.statSync(filePath).size;
37
+ const partCount = Math.ceil(fileSize / partSize);
38
+ const initiateUrl = `${config.asyncAudioUrl}/upload/initiate`;
39
+
40
+ log(`File size: ${fileSize} bytes, parts: ${partCount}`, 'debug');
41
+
42
+ const { data } = await axios.get(initiateUrl, {
43
+ headers: getHeaders(),
44
+ params: { file_name: filePath.split('/').pop(), part_count: partCount },
45
+ timeout: 30000
46
+ });
47
+
48
+ const fileStream = fs.createReadStream(filePath, { highWaterMark: partSize });
49
+ const parts = [];
50
+ let index = 0;
51
+
52
+ for await (const chunk of fileStream) {
53
+ if (!data.presigned_urls || !data.presigned_urls[index]) {
54
+ throw new Error(`Missing presigned URL for part ${index + 1}`);
55
+ }
56
+
57
+ const part = data.presigned_urls[index];
58
+ const partNumber = part.part_number;
59
+ const url = part.url;
60
+
61
+ log(`Uploading part ${index + 1}/${partCount}`, 'debug');
62
+
63
+ const res = await axios.put(url, chunk, {
64
+ headers: { 'Content-Length': chunk.length },
65
+ timeout: 60000 // 1 minute timeout for each part
66
+ });
67
+
68
+ if (!res.headers.etag) {
69
+ throw new Error(`Failed to get ETag for part ${partNumber}`);
70
+ }
71
+
72
+ parts.push({ ETag: res.headers.etag, PartNumber: partNumber });
73
+ index++;
74
+ }
75
+
76
+ log('All parts uploaded, completing multipart upload', 'info');
77
+
78
+ const completeUrl = `${config.asyncAudioUrl}/upload/complete`;
79
+ await axios.post(completeUrl, {
80
+ request_id: data.request_id,
81
+ upload_id: data.upload_id,
82
+ parts: parts.sort((a, b) => a.PartNumber - b.PartNumber)
83
+ }, {
84
+ headers: getHeaders(),
85
+ timeout: 30000
86
+ });
87
+
88
+ log(`Async audio upload completed. Request ID: ${data.request_id}`, 'info');
89
+ return data.request_id;
90
+ } catch (error) {
91
+ log(`Error uploading async audio: ${error.message}`, 'error');
92
+
93
+ if (error.response) {
94
+ throw new Error(`API error (${error.response.status}): ${error.response.data?.message || error.response.statusText}`);
95
+ } else if (error.request) {
96
+ throw new Error('Network error: Unable to reach the API');
97
+ } else {
98
+ throw new Error(`Upload error: ${error.message}`);
99
+ }
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Retrieves emotion prediction results for async audio processing
105
+ * @param {string} requestId - Request ID from uploadAsyncAudio
106
+ * @param {number} maxTries - Maximum polling attempts (default: 20)
107
+ * @param {number} intervalMs - Polling interval in milliseconds (default: 5000)
108
+ * @returns {Promise<Object>} Emotion prediction results
109
+ * @throws {Error} If requestId is invalid or prediction times out
110
+ */
111
+ export async function getEmotions(requestId, maxTries = 20, intervalMs = 5000) {
112
+ // Validation
113
+ if (!requestId || typeof requestId !== 'string') {
114
+ throw new Error('requestId is required and must be a string');
115
+ }
116
+
117
+ if (!config.apiKey) {
118
+ throw new Error('VALENCE_API_KEY is required');
119
+ }
120
+
121
+ if (maxTries < 1 || maxTries > 100) {
122
+ throw new Error('maxTries must be between 1 and 100');
123
+ }
124
+
125
+ if (intervalMs < 1000 || intervalMs > 60000) {
126
+ throw new Error('intervalMs must be between 1000 and 60000');
127
+ }
128
+
129
+ log(`Starting emotion prediction polling for request ${requestId}`, 'info');
130
+
131
+ const url = `${config.asyncAudioUrl}/prediction`;
132
+
133
+ for (let i = 0; i < maxTries; i++) {
134
+ try {
135
+ log(`Polling attempt ${i + 1}/${maxTries}`, 'debug');
136
+
137
+ const res = await axios.get(url, {
138
+ headers: getHeaders(),
139
+ params: { request_id: requestId },
140
+ timeout: 15000
141
+ });
142
+
143
+ if (res.status === 200 && res.data) {
144
+ log('Emotion prediction completed successfully', 'info');
145
+ return res.data;
146
+ }
147
+
148
+ if (res.status === 202) {
149
+ log('Prediction still processing, waiting...', 'debug');
150
+ }
151
+
152
+ } catch (error) {
153
+ if (error.response?.status === 404) {
154
+ throw new Error(`Request ID not found: ${requestId}`);
155
+ } else if (error.response?.status >= 400 && error.response?.status < 500) {
156
+ throw new Error(`Client error (${error.response.status}): ${error.response.data?.message || error.response.statusText}`);
157
+ }
158
+
159
+ log(`Polling error (attempt ${i + 1}): ${error.message}`, 'warn');
160
+ }
161
+
162
+ if (i < maxTries - 1) {
163
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
164
+ }
165
+ }
166
+
167
+ throw new Error(`Prediction not available after ${maxTries} attempts. The request may still be processing.`);
168
+ }
package/src/client.js ADDED
@@ -0,0 +1,18 @@
1
+ import axios from 'axios';
2
+ import { config } from './config.js';
3
+
4
+ /**
5
+ * Returns headers required for API authentication
6
+ * @returns {Object} Headers object with API key
7
+ * @throws {Error} If API key is not configured
8
+ */
9
+ export const getHeaders = () => {
10
+ if (!config.apiKey) {
11
+ throw new Error('VALENCE_API_KEY is required but not configured');
12
+ }
13
+
14
+ return {
15
+ 'x-api-key': config.apiKey,
16
+ 'User-Agent': 'valenceai/0.4.0'
17
+ };
18
+ };