@eternalai-org/sdk
v0.6.0
Published
Official TypeScript SDK for EternalAI - Next-generation API platform with unified access to hundreds of AI models, cashback rewards, and OpenAI-compatible streaming support
Maintainers
Readme
@eternalai-org/sdk
Official TypeScript SDK for EternalAI - Access hundreds of AI models through one unified interface.
Installation
npm install @eternalai-org/sdk
# or
yarn add @eternalai-org/sdk
# or
bun add @eternalai-org/sdkQuick Start
import { EternalAI } from '@eternalai-org/sdk';
const eai = new EternalAI({ apiKey: 'your-api-key' });Chat Completion
// Streaming
const stream = await eai.chat.send({
messages: [{ role: 'user', content: 'Hello!' }],
model: 'openai/gpt-5.1',
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0].delta.content || '');
}
// Non-streaming
const response = await eai.chat.send({
messages: [{ role: 'user', content: 'Hello!' }],
model: 'openai/gpt-5.1',
});
console.log(response.choices[0].message.content);Image Generation
// Flux - Text-to-Image
const image = await eai.chat.send({
messages: [{ role: 'user', content: 'A futuristic city at sunset' }],
model: 'flux/flux-2-pro',
width: 1920,
height: 1080,
});
console.log('Image URL:', image.choices[0].message.content);
// Flux - Image-to-Image (with reference)
const edited = await eai.chat.send({
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Transform into cyberpunk style' },
{ type: 'image_url', image_url: { url: 'https://example.com/photo.jpg' } }
]
}],
model: 'flux/flux-2-pro',
});
// Flux - Blend 2 images
const blended = await eai.chat.send({
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Blend subject with style reference' },
{ type: 'image_url', image_url: { url: 'https://example.com/subject.jpg' } },
{ type: 'image_url', image_url: { url: 'https://example.com/style.jpg' } }
]
}],
model: 'flux/flux-2-pro',
});
// Uncensored AI - Text-to-Image
const uncensored = await eai.chat.send({
messages: [{ role: 'user', content: [{ type: 'text', text: 'A sunset' }] }],
model: 'uncensored-ai/uncensored-image',
type: 'new', // 'new' = text-to-image, 'edit' = image-to-image
lora_config: { 'style-lora': 1 }, // optional LoRA config
});
// Uncensored AI - Image-to-Image
const edited = await eai.chat.send({
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Transform this image' },
{ type: 'image_url', image_url: { url: 'https://example.com/input.jpg', filename: 'input.jpg' } }
]
}],
model: 'uncensored-ai/uncensored-image',
type: 'edit',
image_config: { loras: ['skin', 'lightning'] },
});Video Generation
// Wan - Image-to-Video (via chat.send - auto polls)
// ⚠️ Note: This can take 5-10 minutes as it waits for video generation to complete.
// For browser apps, consider using wan.generate() + getResult() for manual polling with UI updates.
const wanVideo = await eai.chat.send({
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Animate this character' },
{ type: 'image_url', image_url: { url: 'https://example.com/image.png' } }
]
}],
model: 'wan/wan2.5-i2v-preview',
resolution: '480P',
duration: 5,
});
console.log('Video URL:', wanVideo.choices[0].message.content);
// Wan - Direct usage (manual polling - recommended for browser)
const task = await eai.wan.generate({
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Animate this character' },
{ type: 'image_url', image_url: { url: 'https://example.com/image.png' } }
]
}],
model: 'wan/wan2.5-i2v-preview',
resolution: '480P',
duration: 5,
});
// Get task_id and poll manually (good for UI progress updates)
const taskId = task.output?.task_id;
const result = await eai.wan.getResult(taskId);
if (result.output?.task_status === 'SUCCEEDED') {
console.log('Video URL:', result.output.video_url);
}
// Uncensored AI - Video generation
const uncensoredVideo = await eai.chat.send({
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Animate this image' },
{ type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
]
}],
model: 'uncensored-ai/uncensored-video',
type: 'edit',
is_magic_prompt: true,
duration: 5,
audio: true,
video_config: { loras: ['flip', 'nsfw'] },
});AI Search
const search = await eai.chat.send({
messages: [{ role: 'user', content: 'Latest AI news 2024' }],
model: 'tavily/search',
});
console.log(search.choices[0].message.content);Supported Providers
| Provider | Model Format | Type |
|----------|-------------|------|
| OpenAI | openai/gpt-5.1 | Chat |
| Claude | anthropic/claude-* | Chat |
| Gemini | gemini/gemini-* | Chat |
| GLM | glm/glm-4.5-flash | Chat |
| Grok | xai/grok-* | Chat |
| Mistral | mistralai/devstral-2512 | Chat |
| Qwen | qwen/qwen-* | Chat |
| Flux | flux/flux-2-pro | Image |
| Uncensored AI | uncensored-ai/uncensored-image | Image |
| Uncensored AI | uncensored-ai/uncensored-video | Video |
| Wan | wan/wan2.5-i2v-preview | Video |
| Tavily | tavily/search | Search |
| Nano Banana | nano-banana/gemini-* | Chat + Image |
Advanced Usage
Flux - Manual Polling (Recommended for Browser)
// Step 1: Start generation - returns immediately with polling_url
const task = await eai.flux.generate({
messages: [{ role: 'user', content: 'A mountain landscape' }],
model: 'flux/flux-2-pro',
width: 1024,
height: 1024,
safety_tolerance: 2,
});
console.log('Task started:', task.polling_url);
// Step 2: Poll for result
const result = await eai.flux.pollResult(task.polling_url, {
interval: 3000,
maxAttempts: 60,
onStatusUpdate: (status, attempt) => console.log(`[${attempt}] ${status}`),
});
console.log('Image URL:', result.result?.sample);
// Or check status manually
const status = await eai.flux.getResult(task.polling_url);
if (status.status === 'Ready') {
console.log('Image URL:', status.result?.sample);
}Wan - Manual Polling (Recommended for Browser)
// Step 1: Start generation - returns immediately with task_id
const task = await eai.wan.generate(
{
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Animate this character...' },
{ type: 'image_url', image_url: { url: 'https://...' } }
]
}],
model: 'wan/wan2.5-i2v-preview',
resolution: '480P',
duration: 5,
}
);
console.log('Task started:', task.output?.task_id);
// Step 2a: Poll manually (good for custom UI updates)
const taskId = task.output?.task_id;
let result = await eai.wan.getResult(taskId);
while (result.output?.task_status === 'RUNNING' || result.output?.task_status === 'PENDING') {
await new Promise(r => setTimeout(r, 5000)); // wait 5s
result = await eai.wan.getResult(taskId);
console.log('Status:', result.output?.task_status);
}
console.log('Video URL:', result.output?.video_url);
// Step 2b: Or use pollResult for auto-polling with callbacks
const finalResult = await eai.wan.pollResult(taskId, {
interval: 5000,
maxAttempts: 120,
onStatusUpdate: (status, attempt) => console.log(`[${attempt}] ${status}`),
});
console.log('Video URL:', finalResult.output?.video_url);Uncensored AI - Manual Polling (Recommended for Browser)
// Step 1: Start generation - returns immediately with request_id
const task = await eai.uncensoredAI.generate({
messages: [{ role: 'user', content: [{ type: 'text', text: 'A sunset' }] }],
model: 'uncensored-ai/uncensored-image',
type: 'new',
lora_config: { 'style-lora': 1 },
});
console.log('Task started:', task.request_id);
// Step 2: Poll for result
const result = await eai.uncensoredAI.pollResult(task.request_id, 'uncensored-image', {
interval: 3000,
maxAttempts: 60,
onStatusUpdate: (status, attempt) => console.log(`[${attempt}] ${status}`),
});
console.log('URL:', result.result_url);
// Or check status manually
const status = await eai.uncensoredAI.getResult(task.request_id, 'uncensored-image');
if (status.status === 'success') {
console.log('URL:', status.result_url);
}API Reference
new EternalAI(config)
const eai = new EternalAI({
apiKey: 'your-api-key',
timeout: 30000, // optional, in ms
});eai.chat.send(request)
Universal method for all providers. Auto-routes based on model prefix.
| Parameter | Type | Description |
|-----------|------|-------------|
| messages | ChatMessage[] | Array of messages |
| model | string | Provider/model (e.g., openai/gpt-5.1) |
| stream | boolean | Enable streaming (default: false) |
| width | number | Image width (Flux only) |
| height | number | Image height (Flux only) |
| safety_tolerance | number | 0-6 (Flux only, default: 2) |
| resolution | string | 480P/720P/1080P (Wan only) |
| duration | number | Video duration in seconds (Wan only) |
Returns: ChatCompletionResponse or AsyncIterable<ChatCompletionChunk> (if streaming)
eai.flux.generate(request)
Start image generation. Returns immediately with polling_url.
Returns: FluxGenerateResponse with polling_url
eai.flux.getResult(pollingUrl)
Get current task status and result.
Returns: FluxResultResponse with status and result.sample
eai.flux.pollResult(pollingUrl, pollingOptions)
Auto-poll until completion with callbacks.
Returns: FluxResultResponse with result.sample (image URL)
eai.wan.generate(request)
Start video generation. Returns immediately with task_id.
Returns: WanTaskResponse with output.task_id
eai.wan.getResult(taskId)
Get current task status and result.
Returns: WanResultResponse with output.task_status and output.video_url
eai.wan.pollResult(taskId, pollingOptions)
Auto-poll until completion with callbacks.
Returns: WanResultResponse with output.video_url (video URL)
eai.uncensoredAI.generate(request)
Start image/video generation. Returns immediately with request_id.
Returns: UncensoredGenerateResponse with request_id
eai.uncensoredAI.getResult(requestId, endpoint)
Get current task status and result.
Returns: UncensoredResultResponse with status and result_url
eai.uncensoredAI.pollResult(requestId, endpoint, pollingOptions)
Auto-poll until completion with callbacks.
Returns: UncensoredResultResponse with result_url
TypeScript
All types are exported:
import type {
EternalAIConfig,
ChatMessage,
ChatCompletionRequest,
ChatCompletionResponse,
ChatCompletionChunk,
} from '@eternalai-org/sdk';Error Handling
try {
const result = await eai.chat.send({ ... });
} catch (error) {
console.error('Error:', error.message);
}Environment Support
- Node.js 18+
- Bun 1.0+
- Browser (with bundler)
Links
License
MIT
