@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 +422 -0
- package/lib/index.d.ts +58 -0
- package/lib/index.js +311 -0
- package/package.json +13 -0
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
|
+
}
|