@ceo-ai/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 ADDED
@@ -0,0 +1,422 @@
1
+ ## SDK README
2
+
3
+ ### `ceo-sdk/README.md`
4
+
5
+
6
+ ## @ceo-ai/sdk
7
+
8
+ Lightweight Node.js SDK for the CEO.AI API. Zero external dependencies — uses only Node.js built-in modules.
9
+
10
+ ## Installation
11
+
12
+
13
+ ```bash
14
+ npm install @ceo-ai/sdk
15
+ ```
16
+
17
+
18
+ ## Quick Start
19
+
20
+
21
+ ```javascript
22
+ const { CeoAI } = require('@ceo-ai/sdk');
23
+
24
+ const ceo = new CeoAI({ apiKey: 'sk_live_your_api_key_here' });
25
+
26
+ // Send a prompt and wait for the result
27
+ const { response, metadata } = await ceo.promptAndWait('What was our Q4 revenue?');
28
+
29
+ console.log(response);
30
+ // => { answer: "Q4 revenue was $12.5M, representing a 15% increase..." }
31
+
32
+ console.log(metadata);
33
+ // => { agentId: '...', agentName: 'Financial Analyst', model: 'claude-sonnet-4-5', estimatedCredits: 10}
34
+ ```
35
+
36
+
37
+ ## API Reference
38
+
39
+ ### `new CeoAI(options)`
40
+
41
+ Create a new client instance.
42
+
43
+
44
+ ```javascript
45
+ const ceo = new CeoAI({
46
+ apiKey: 'sk_live_...', // Required
47
+ endpoint: 'https://...', // Optional. Default: 'https://ingest.api.ceo.ai'
48
+ timeout: 30000, // Optional. Request timeout in ms. Default: 30000
49
+ pollInterval: 2000, // Optional. Default polling interval in ms. Default: 2000
50
+ pollTimeout: 120000 // Optional. Default polling timeout in ms. Default: 120000
51
+ });
52
+ ```
53
+
54
+
55
+ | Option | Type | Required | Default | Description |
56
+ |--------|------|----------|---------|-------------|
57
+ | `apiKey` | `string` | ✅ | — | Your API key (`sk_live_...`) |
58
+ | `endpoint` | `string` | — | `https://ingest.api.ceo.ai` | API endpoint URL |
59
+ | `timeout` | `number` | — | `30000` | HTTP request timeout in ms |
60
+ | `pollInterval` | `number` | — | `2000` | Default interval between poll attempts in ms |
61
+ | `pollTimeout` | `number` | — | `120000` | Default max time to wait when polling in ms |
62
+
63
+ ---
64
+
65
+ ### `ceo.prompt(prompt, options?)`
66
+
67
+ Send a prompt to your AI agent. Returns immediately with a presigned URL where results will be written.
68
+
69
+
70
+ ```javascript
71
+ const result = await ceo.prompt('What was our Q4 revenue?');
72
+
73
+ console.log(result);
74
+ // {
75
+ // presignedUrl: 'https://ceo-ai-api-output-results.s3.amazonaws.com/...',
76
+ // message: 'Processing request',
77
+ // estimatedCredits: 10,
78
+ // agentId: 'abc-123',
79
+ // agentName: 'Financial Analyst',
80
+ // model: 'claude-sonnet-4-5',
81
+ // tenantId: 'tenant-456'
82
+ // }
83
+ ```
84
+
85
+
86
+ **Parameters:**
87
+
88
+ | Parameter | Type | Required | Description |
89
+ |-----------|------|----------|-------------|
90
+ | `prompt` | `string` | ✅ | The prompt text to send |
91
+ | `options.ragMode` | `boolean` | — | Enable/disable RAG. Default: `true` (server-side) |
92
+ | `options.conversationHistory` | `Array` | — | Previous messages for context |
93
+
94
+ **Returns:** `Promise<PromptResponse>`
95
+
96
+ ---
97
+
98
+ ### `ceo.promptAndWait(prompt, options?)`
99
+
100
+ Send a prompt and automatically poll until the result is ready. This is the most convenient method for typical usage.
101
+
102
+
103
+ ```javascript
104
+ const { response, metadata } = await ceo.promptAndWait('What was our Q4 revenue?', {
105
+ pollInterval: 3000,
106
+ pollTimeout: 60000,
107
+ onPoll: (attempt, elapsed) => {
108
+ console.log(`Waiting... attempt ${attempt} (${elapsed}ms)`);
109
+ }
110
+ });
111
+
112
+ console.log(response); // The AI response
113
+ console.log(metadata); // Agent/model/credits info
114
+ ```
115
+
116
+
117
+ **Parameters:**
118
+
119
+ | Parameter | Type | Required | Description |
120
+ |-----------|------|----------|-------------|
121
+ | `prompt` | `string` | ✅ | The prompt text to send |
122
+ | `options.ragMode` | `boolean` | — | Enable/disable RAG |
123
+ | `options.conversationHistory` | `Array` | — | Previous messages for context |
124
+ | `options.pollInterval` | `number` | — | Override polling interval in ms |
125
+ | `options.pollTimeout` | `number` | — | Override polling timeout in ms |
126
+ | `options.onPoll` | `Function` | — | Callback: `(attempt, elapsed) => {}` |
127
+
128
+ **Returns:** `Promise<{ response: any, metadata: Object }>`
129
+
130
+ ---
131
+
132
+ ### `ceo.pollForResult(presignedUrl, options?)`
133
+
134
+ Poll a presigned URL until content is available. Useful when you've stored a presigned URL from a previous `prompt()` call and want to check for results later.
135
+
136
+
137
+ ```javascript
138
+ const result = await ceo.pollForResult(presignedUrl, {
139
+ interval: 3000,
140
+ timeout: 60000,
141
+ onPoll: (attempt, elapsed) => {
142
+ console.log(`Attempt ${attempt} (${elapsed}ms elapsed)`);
143
+ }
144
+ });
145
+ ```
146
+
147
+
148
+ **Parameters:**
149
+
150
+ | Parameter | Type | Required | Description |
151
+ |-----------|------|----------|-------------|
152
+ | `presignedUrl` | `string` | ✅ | The presigned URL to poll |
153
+ | `options.interval` | `number` | — | Polling interval in ms |
154
+ | `options.timeout` | `number` | — | Max wait time in ms |
155
+ | `options.onPoll` | `Function` | — | Callback: `(attempt, elapsed) => {}` |
156
+
157
+ **Returns:** `Promise<Object | string>` — Parsed JSON if possible, raw string otherwise.
158
+
159
+ ---
160
+
161
+ ### `CeoAPIError`
162
+
163
+ Custom error class thrown for API errors.
164
+
165
+
166
+ ```javascript
167
+ const { CeoAI, CeoAPIError } = require('@ceo-ai/sdk');
168
+
169
+ try {
170
+ await ceo.promptAndWait('Hello');
171
+ } catch (error) {
172
+ if (error instanceof CeoAPIError) {
173
+ console.log(error.message); // Error message
174
+ console.log(error.statusCode); // HTTP status code
175
+ console.log(error.details); // Additional details (if any)
176
+
177
+ // Convenience getters
178
+ error.isAuthError; // true if 401
179
+ error.isInsufficientCredits; // true if 402
180
+ error.isRateLimited; // true if 429
181
+ error.isTimeout; // true if 408
182
+ }
183
+ }
184
+ ```
185
+
186
+
187
+ ## Usage Examples
188
+
189
+ ### Simple Query
190
+
191
+
192
+ ```javascript
193
+ const { CeoAI } = require('@ceo-ai/sdk');
194
+
195
+ const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
196
+
197
+ const { response } = await ceo.promptAndWait('Summarize our sales this quarter');
198
+ console.log(response);
199
+ ```
200
+
201
+
202
+ ### Fire and Forget (Store URL for Later)
203
+
204
+
205
+ ```javascript
206
+ const { CeoAI } = require('@ceo-ai/sdk');
207
+
208
+ const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
209
+
210
+ // Submit the prompt
211
+ const submission = await ceo.prompt('Generate a detailed report');
212
+ console.log('Presigned URL:', submission.presignedUrl);
213
+
214
+ // Store submission.presignedUrl in your database
215
+ // ...
216
+
217
+ // Later (even in a different process), retrieve the result:
218
+ const result = await ceo.pollForResult(storedPresignedUrl);
219
+ console.log(result);
220
+ ```
221
+
222
+
223
+ ### Conversation History
224
+
225
+
226
+ ```javascript
227
+ const { CeoAI } = require('@ceo-ai/sdk');
228
+
229
+ const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
230
+
231
+ // First message
232
+ const first = await ceo.promptAndWait('What was our Q4 revenue?');
233
+ console.log(first.response);
234
+
235
+ // Follow-up with context
236
+ const second = await ceo.promptAndWait('How does that compare to Q3?', {
237
+ conversationHistory: [
238
+ { role: 'user', content: 'What was our Q4 revenue?' },
239
+ { role: 'assistant', content: JSON.stringify(first.response) }
240
+ ]
241
+ });
242
+ console.log(second.response);
243
+ ```
244
+
245
+
246
+ ### Disable RAG Mode
247
+
248
+
249
+ ```javascript
250
+ const { response } = await ceo.promptAndWait('What is 2 + 2?', {
251
+ ragMode: false
252
+ });
253
+ ```
254
+
255
+
256
+ ### Progress Tracking
257
+
258
+
259
+ ```javascript
260
+ const { response } = await ceo.promptAndWait('Complex analysis...', {
261
+ pollInterval: 3000,
262
+ pollTimeout: 300000, // 5 minutes
263
+ onPoll: (attempt, elapsed) => {
264
+ const seconds = Math.round(elapsed / 1000);
265
+ console.log(`⏳ Waiting for response... ${seconds}s (attempt ${attempt})`);
266
+ }
267
+ });
268
+ ```
269
+
270
+
271
+ ### Error Handling
272
+
273
+
274
+ ```javascript
275
+ const { CeoAI, CeoAPIError } = require('@ceo-ai/sdk');
276
+
277
+ const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
278
+
279
+ try {
280
+ const { response } = await ceo.promptAndWait('What was our Q4 revenue?');
281
+ console.log(response);
282
+
283
+ } catch (error) {
284
+ if (error instanceof CeoAPIError) {
285
+ switch (true) {
286
+ case error.isAuthError:
287
+ console.error('❌ Invalid or expired API key');
288
+ console.error(' Get a new key at https://app.ceo.ai/apikeys');
289
+ break;
290
+
291
+ case error.isInsufficientCredits:
292
+ console.error('❌ Insufficient credits');
293
+ console.error(` Required: ${error.details?.requiredCredits}`);
294
+ console.error(` Available: ${error.details?.availableCredits}`);
295
+ console.error(' Add credits at https://app.ceo.ai/usercredits');
296
+ break;
297
+
298
+ case error.isTimeout:
299
+ console.error('❌ Request timed out');
300
+ console.error(' Try increasing pollTimeout');
301
+ break;
302
+
303
+ case error.isRateLimited:
304
+ console.error('❌ Rate limited. Please wait and try again.');
305
+ break;
306
+
307
+ default:
308
+ console.error(`❌ API error (${error.statusCode}): ${error.message}`);
309
+ }
310
+ } else {
311
+ console.error('❌ Unexpected error:', error.message);
312
+ }
313
+ }
314
+ ```
315
+
316
+
317
+ ### Express.js Integration
318
+
319
+
320
+ ```javascript
321
+ const express = require('express');
322
+ const { CeoAI, CeoAPIError } = require('@ceo-ai/sdk');
323
+
324
+ const app = express();
325
+ app.use(express.json());
326
+
327
+ const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
328
+
329
+ // Async endpoint — returns presigned URL
330
+ app.post('/api/ask', async (req, res) => {
331
+ try {
332
+ const result = await ceo.prompt(req.body.question);
333
+ res.json({
334
+ presignedUrl: result.presignedUrl,
335
+ estimatedCredits: result.estimatedCredits
336
+ });
337
+ } catch (error) {
338
+ if (error instanceof CeoAPIError) {
339
+ res.status(error.statusCode).json({ error: error.message });
340
+ } else {
341
+ res.status(500).json({ error: 'Internal server error' });
342
+ }
343
+ }
344
+ });
345
+
346
+ // Sync endpoint — waits for result
347
+ app.post('/api/ask/sync', async (req, res) => {
348
+ try {
349
+ const { response, metadata } = await ceo.promptAndWait(req.body.question, {
350
+ pollTimeout: 60000
351
+ });
352
+ res.json({ response, metadata });
353
+ } catch (error) {
354
+ if (error instanceof CeoAPIError) {
355
+ res.status(error.statusCode).json({
356
+ error: error.message,
357
+ details: error.details
358
+ });
359
+ } else {
360
+ res.status(500).json({ error: 'Internal server error' });
361
+ }
362
+ }
363
+ });
364
+
365
+ app.listen(3000);
366
+ ```
367
+
368
+
369
+ ### Batch Processing
370
+
371
+
372
+ ```javascript
373
+ const { CeoAI } = require('@ceo-ai/sdk');
374
+
375
+ const ceo = new CeoAI({ apiKey: process.env.CEO_API_KEY });
376
+
377
+ const questions = [
378
+ 'What was Q1 revenue?',
379
+ 'What was Q2 revenue?',
380
+ 'What was Q3 revenue?',
381
+ 'What was Q4 revenue?'
382
+ ];
383
+
384
+ // Submit all prompts concurrently
385
+ const submissions = await Promise.all(
386
+ questions.map(q => ceo.prompt(q))
387
+ );
388
+
389
+ console.log(`Submitted ${submissions.length} prompts`);
390
+
391
+ // Wait a bit, then collect all results
392
+ await new Promise(r => setTimeout(r, 10000));
393
+
394
+ const results = await Promise.all(
395
+ submissions.map(s => ceo.pollForResult(s.presignedUrl, {
396
+ timeout: 60000
397
+ }))
398
+ );
399
+
400
+ results.forEach((result, i) => {
401
+ console.log(`\n--- ${questions[i]} ---`);
402
+ console.log(result);
403
+ });
404
+ ```
405
+
406
+
407
+ ## Getting Your API Key
408
+
409
+ 1. Log in to the [CEO.AI Dashboard](https://app.ceo.ai)
410
+ 2. Navigate to **API Keys**
411
+ 3. Click **Create API Key** (NB: you must have a paid subscription to create api keys)
412
+ 4. Select the agent you want to use
413
+ 5. Copy the generated key — it will only be shown once
414
+
415
+ ## Requirements
416
+
417
+ - Node.js 16 or later
418
+ - Zero external dependencies
419
+
420
+ ## License
421
+
422
+ MIT
package/lib/index.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ export interface CeoAIOptions {
2
+ apiKey: string;
3
+ endpoint?: string;
4
+ timeout?: number;
5
+ pollInterval?: number;
6
+ pollTimeout?: number;
7
+ }
8
+
9
+ export interface PromptOptions {
10
+ ragMode?: boolean;
11
+ conversationHistory?: Array<{ role: string; content: string }>;
12
+ }
13
+
14
+ export interface PromptResponse {
15
+ presignedUrl: string;
16
+ message: string;
17
+ estimatedCredits: number;
18
+ agentId: string;
19
+ agentName: string;
20
+ model: string;
21
+ tenantId: string;
22
+ }
23
+
24
+ export interface PollOptions {
25
+ interval?: number;
26
+ timeout?: number;
27
+ onPoll?: (attempt: number, elapsed: number) => void;
28
+ }
29
+
30
+ export interface PromptAndWaitOptions extends PromptOptions, PollOptions {}
31
+
32
+ export interface PromptAndWaitResponse {
33
+ response: any;
34
+ metadata: {
35
+ agentId: string;
36
+ agentName: string;
37
+ model: string;
38
+ estimatedCredits: number;
39
+ tenantId: string;
40
+ };
41
+ }
42
+
43
+ export declare class CeoAPIError extends Error {
44
+ statusCode: number;
45
+ details: any;
46
+ readonly isAuthError: boolean;
47
+ readonly isInsufficientCredits: boolean;
48
+ readonly isRateLimited: boolean;
49
+ readonly isTimeout: boolean;
50
+ constructor(message: string, statusCode: number, details?: any);
51
+ }
52
+
53
+ export declare class CeoAI {
54
+ constructor(options: CeoAIOptions);
55
+ prompt(prompt: string, options?: PromptOptions): Promise<PromptResponse>;
56
+ promptAndWait(prompt: string, options?: PromptAndWaitOptions): Promise<PromptAndWaitResponse>;
57
+ pollForResult(presignedUrl: string, options?: PollOptions): Promise<any>;
58
+ }
package/lib/index.js ADDED
@@ -0,0 +1,311 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ class CeoAI {
5
+ /**
6
+ * @param {Object} options
7
+ * @param {string} options.apiKey - Your API key (sk_live_...)
8
+ * @param {string} [options.endpoint] - API endpoint URL
9
+ * @param {number} [options.timeout] - Request timeout in ms
10
+ * @param {number} [options.pollInterval] - Default polling interval in ms
11
+ * @param {number} [options.pollTimeout] - Default polling timeout in ms
12
+ */
13
+ constructor(options = {}) {
14
+ if (!options.apiKey) {
15
+ throw new Error('apiKey is required');
16
+ }
17
+
18
+ if (!options.apiKey.startsWith('sk_live_')) {
19
+ throw new Error('Invalid API key format. Expected sk_live_...');
20
+ }
21
+
22
+ this.apiKey = options.apiKey;
23
+ this.endpoint = options.endpoint || 'https://ingestion.api.ceo.ai';
24
+ this.timeout = options.timeout || 30000;
25
+ this.pollInterval = options.pollInterval || 2000;
26
+ this.pollTimeout = options.pollTimeout || 120000;
27
+ }
28
+
29
+ /**
30
+ * Send a prompt to your agent.
31
+ * Returns the presigned URL and metadata without waiting for results.
32
+ *
33
+ * @param {string} prompt - The prompt text
34
+ * @param {Object} [options]
35
+ * @param {boolean} [options.ragMode] - Enable/disable RAG (defaults to true on server)
36
+ * @param {Array} [options.conversationHistory] - Previous conversation messages
37
+ * @returns {Promise<PromptResponse>}
38
+ *
39
+ * @example
40
+ * const ceo = new CeoAI({ apiKey: 'sk_live_...' });
41
+ * const result = await ceo.prompt('What was Q4 revenue?');
42
+ * console.log(result.presignedUrl);
43
+ */
44
+ async prompt(prompt, options = {}) {
45
+ if (!prompt || typeof prompt !== 'string') {
46
+ throw new Error('prompt must be a non-empty string');
47
+ }
48
+
49
+ const body = {
50
+ prompt,
51
+ ...(options.ragMode !== undefined && { ragMode: options.ragMode }),
52
+ ...(options.conversationHistory && { conversationHistory: options.conversationHistory })
53
+ };
54
+
55
+ const response = await this._request('POST', this.endpoint, body);
56
+
57
+ if (response.error) {
58
+ throw new CeoAPIError(
59
+ response.error || response.message,
60
+ response.code || response.statusCode,
61
+ response.details
62
+ );
63
+ }
64
+
65
+ return response.data || response;
66
+ }
67
+
68
+ /**
69
+ * Send a prompt and wait for the result.
70
+ * This is a convenience method that combines prompt() + pollForResult().
71
+ *
72
+ * @param {string} prompt - The prompt text
73
+ * @param {Object} [options]
74
+ * @param {boolean} [options.ragMode] - Enable/disable RAG
75
+ * @param {Array} [options.conversationHistory] - Previous conversation messages
76
+ * @param {number} [options.pollInterval] - Polling interval in ms
77
+ * @param {number} [options.pollTimeout] - Max wait time in ms
78
+ * @param {Function} [options.onPoll] - Callback on each poll attempt: (attempt, elapsed) => {}
79
+ * @returns {Promise<Object>} The AI response
80
+ *
81
+ * @example
82
+ * const ceo = new CeoAI({ apiKey: 'sk_live_...' });
83
+ * const result = await ceo.promptAndWait('What was Q4 revenue?');
84
+ * console.log(result);
85
+ */
86
+ async promptAndWait(prompt, options = {}) {
87
+ const {
88
+ ragMode,
89
+ conversationHistory,
90
+ pollInterval,
91
+ pollTimeout,
92
+ onPoll
93
+ } = options;
94
+
95
+ const submission = await this.prompt(prompt, { ragMode, conversationHistory });
96
+
97
+ if (!submission.presignedUrl) {
98
+ throw new Error('No presigned URL returned from API');
99
+ }
100
+
101
+ const result = await this.pollForResult(submission.presignedUrl, {
102
+ interval: pollInterval,
103
+ timeout: pollTimeout,
104
+ onPoll
105
+ });
106
+
107
+ return {
108
+ response: result,
109
+ metadata: {
110
+ agentId: submission.agentId,
111
+ agentName: submission.agentName,
112
+ model: submission.model,
113
+ estimatedCredits: submission.estimatedCredits,
114
+ tenantId: submission.tenantId
115
+ }
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Poll a presigned URL until content is available.
121
+ *
122
+ * @param {string} presignedUrl - The presigned URL to poll
123
+ * @param {Object} [options]
124
+ * @param {number} [options.interval] - Polling interval in ms
125
+ * @param {number} [options.timeout] - Max wait time in ms
126
+ * @param {Function} [options.onPoll] - Callback on each attempt: (attempt, elapsed) => {}
127
+ * @returns {Promise<Object|string>} The result content
128
+ *
129
+ * @example
130
+ * const result = await ceo.pollForResult(presignedUrl, {
131
+ * interval: 3000,
132
+ * timeout: 60000,
133
+ * onPoll: (attempt) => console.log(`Attempt ${attempt}...`)
134
+ * });
135
+ */
136
+ async pollForResult(presignedUrl, options = {}) {
137
+ const {
138
+ interval = this.pollInterval,
139
+ timeout = this.pollTimeout,
140
+ onPoll = null
141
+ } = options;
142
+
143
+ const startTime = Date.now();
144
+ let attempt = 0;
145
+
146
+ while (Date.now() - startTime < timeout) {
147
+ attempt++;
148
+ const elapsed = Date.now() - startTime;
149
+
150
+ if (onPoll) {
151
+ onPoll(attempt, elapsed);
152
+ }
153
+
154
+ try {
155
+ const result = await this._fetch(presignedUrl);
156
+ if (result !== null) {
157
+ try {
158
+ return JSON.parse(result);
159
+ } catch {
160
+ return result;
161
+ }
162
+ }
163
+ } catch (error) {
164
+ // 403/404 = object not written yet, keep polling
165
+ if (error.statusCode === 403 || error.statusCode === 404) {
166
+ // Expected — not ready yet
167
+ } else {
168
+ throw error;
169
+ }
170
+ }
171
+
172
+ await this._sleep(interval);
173
+ }
174
+
175
+ throw new CeoAPIError(
176
+ `Polling timed out after ${timeout}ms (${attempt} attempts)`,
177
+ 408
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Make an HTTP request to the API
183
+ * @private
184
+ */
185
+ _request(method, url, body = null) {
186
+ return new Promise((resolve, reject) => {
187
+ const parsedUrl = new URL(url);
188
+ const isHttps = parsedUrl.protocol === 'https:';
189
+ const lib = isHttps ? https : http;
190
+
191
+ const payload = body ? JSON.stringify(body) : null;
192
+
193
+ const options = {
194
+ hostname: parsedUrl.hostname,
195
+ port: parsedUrl.port || (isHttps ? 443 : 80),
196
+ path: parsedUrl.pathname + parsedUrl.search,
197
+ method,
198
+ headers: {
199
+ 'Authorization': `Bearer ${this.apiKey}`,
200
+ 'Content-Type': 'application/json',
201
+ 'User-Agent': `ceo-ai-sdk-node/1.0.0`,
202
+ ...(payload && { 'Content-Length': Buffer.byteLength(payload) })
203
+ }
204
+ };
205
+
206
+ const req = lib.request(options, (res) => {
207
+ let data = '';
208
+ res.on('data', (chunk) => { data += chunk; });
209
+ res.on('end', () => {
210
+ try {
211
+ const parsed = JSON.parse(data);
212
+ if (res.statusCode >= 400) {
213
+ parsed.statusCode = res.statusCode;
214
+ }
215
+ resolve(parsed);
216
+ } catch {
217
+ resolve({ data: data, statusCode: res.statusCode });
218
+ }
219
+ });
220
+ });
221
+
222
+ req.on('error', reject);
223
+ req.setTimeout(this.timeout, () => {
224
+ req.destroy();
225
+ reject(new CeoAPIError('Request timed out', 408));
226
+ });
227
+
228
+ if (payload) req.write(payload);
229
+ req.end();
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Fetch a URL (for presigned URL polling)
235
+ * @private
236
+ */
237
+ _fetch(url) {
238
+ return new Promise((resolve, reject) => {
239
+ const parsedUrl = new URL(url);
240
+ const isHttps = parsedUrl.protocol === 'https:';
241
+ const lib = isHttps ? https : http;
242
+
243
+ const req = lib.get(url, (res) => {
244
+ if (res.statusCode === 403 || res.statusCode === 404) {
245
+ const error = new CeoAPIError('Not ready', res.statusCode);
246
+ res.resume();
247
+ reject(error);
248
+ return;
249
+ }
250
+
251
+ let data = '';
252
+ res.on('data', (chunk) => { data += chunk; });
253
+ res.on('end', () => {
254
+ if (res.statusCode >= 200 && res.statusCode < 300 && data.length > 0) {
255
+ resolve(data);
256
+ } else {
257
+ resolve(null);
258
+ }
259
+ });
260
+ });
261
+
262
+ req.on('error', reject);
263
+ req.setTimeout(10000, () => {
264
+ req.destroy();
265
+ reject(new CeoAPIError('Fetch timed out', 408));
266
+ });
267
+ });
268
+ }
269
+
270
+ /**
271
+ * @private
272
+ */
273
+ _sleep(ms) {
274
+ return new Promise((resolve) => setTimeout(resolve, ms));
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Custom error class for CEO.AI API errors
280
+ */
281
+ class CeoAPIError extends Error {
282
+ /**
283
+ * @param {string} message
284
+ * @param {number} statusCode
285
+ * @param {Object} [details]
286
+ */
287
+ constructor(message, statusCode, details = null) {
288
+ super(message);
289
+ this.name = 'CeoAPIError';
290
+ this.statusCode = statusCode;
291
+ this.details = details;
292
+ }
293
+
294
+ get isAuthError() {
295
+ return this.statusCode === 401;
296
+ }
297
+
298
+ get isInsufficientCredits() {
299
+ return this.statusCode === 402;
300
+ }
301
+
302
+ get isRateLimited() {
303
+ return this.statusCode === 429;
304
+ }
305
+
306
+ get isTimeout() {
307
+ return this.statusCode === 408;
308
+ }
309
+ }
310
+
311
+ module.exports = { CeoAI, CeoAPIError };
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@ceo-ai/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Node.js SDK for CEO.AI API",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": [
8
+ "lib/"
9
+ ],
10
+ "scripts":{"test":"node test/index.test.js"},
11
+ "keywords": ["ceo-ai", "sdk", "ai", "api", "ai-agents"],
12
+ "license": "MIT"
13
+ }