@loonylabs/tti-middleware 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/.env.example +45 -0
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/middleware/services/tti/index.d.ts +2 -0
- package/dist/middleware/services/tti/index.js +18 -0
- package/dist/middleware/services/tti/providers/base-tti-provider.d.ts +98 -0
- package/dist/middleware/services/tti/providers/base-tti-provider.js +306 -0
- package/dist/middleware/services/tti/providers/edenai-provider.d.ts +28 -0
- package/dist/middleware/services/tti/providers/edenai-provider.js +181 -0
- package/dist/middleware/services/tti/providers/google-cloud-provider.d.ts +60 -0
- package/dist/middleware/services/tti/providers/google-cloud-provider.js +473 -0
- package/dist/middleware/services/tti/providers/index.d.ts +4 -0
- package/dist/middleware/services/tti/providers/index.js +22 -0
- package/dist/middleware/services/tti/providers/ionos-provider.d.ts +26 -0
- package/dist/middleware/services/tti/providers/ionos-provider.js +134 -0
- package/dist/middleware/services/tti/tti.service.d.ts +70 -0
- package/dist/middleware/services/tti/tti.service.js +162 -0
- package/dist/middleware/types/index.d.ts +196 -0
- package/dist/middleware/types/index.js +37 -0
- package/package.json +90 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base TTI Provider Abstract Class
|
|
3
|
+
*
|
|
4
|
+
* All providers must extend this class and implement the ITTIProvider interface.
|
|
5
|
+
* Provides common functionality for error handling, logging, and validation.
|
|
6
|
+
*/
|
|
7
|
+
import { TTIProvider, TTIRequest, TTIResponse, TTIErrorCode, ITTIProvider, ModelInfo, RetryOptions, LogLevel } from '../../../types';
|
|
8
|
+
export declare class TTIError extends Error {
|
|
9
|
+
readonly provider: string;
|
|
10
|
+
readonly code: TTIErrorCode;
|
|
11
|
+
readonly cause?: Error | undefined;
|
|
12
|
+
constructor(provider: string, code: TTIErrorCode, message: string, cause?: Error | undefined);
|
|
13
|
+
toString(): string;
|
|
14
|
+
}
|
|
15
|
+
export declare class InvalidConfigError extends TTIError {
|
|
16
|
+
constructor(provider: string, message: string, cause?: Error);
|
|
17
|
+
}
|
|
18
|
+
export declare class QuotaExceededError extends TTIError {
|
|
19
|
+
constructor(provider: string, message?: string, cause?: Error);
|
|
20
|
+
}
|
|
21
|
+
export declare class ProviderUnavailableError extends TTIError {
|
|
22
|
+
constructor(provider: string, message?: string, cause?: Error);
|
|
23
|
+
}
|
|
24
|
+
export declare class GenerationFailedError extends TTIError {
|
|
25
|
+
constructor(provider: string, message: string, cause?: Error);
|
|
26
|
+
}
|
|
27
|
+
export declare class NetworkError extends TTIError {
|
|
28
|
+
constructor(provider: string, message: string, cause?: Error);
|
|
29
|
+
}
|
|
30
|
+
export declare class CapabilityNotSupportedError extends TTIError {
|
|
31
|
+
constructor(provider: string, capability: string, model: string, cause?: Error);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Set the global log level for all TTI providers
|
|
35
|
+
*/
|
|
36
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get the current global log level
|
|
39
|
+
*/
|
|
40
|
+
export declare function getLogLevel(): LogLevel;
|
|
41
|
+
export declare abstract class BaseTTIProvider implements ITTIProvider {
|
|
42
|
+
protected readonly providerName: TTIProvider;
|
|
43
|
+
constructor(providerName: TTIProvider);
|
|
44
|
+
abstract getDisplayName(): string;
|
|
45
|
+
abstract listModels(): ModelInfo[];
|
|
46
|
+
abstract getDefaultModel(): string;
|
|
47
|
+
abstract generate(request: TTIRequest): Promise<TTIResponse>;
|
|
48
|
+
getName(): TTIProvider;
|
|
49
|
+
/**
|
|
50
|
+
* Get model info by ID
|
|
51
|
+
*/
|
|
52
|
+
protected getModelInfo(modelId: string): ModelInfo | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Check if a model supports a specific capability
|
|
55
|
+
*/
|
|
56
|
+
protected modelSupportsCapability(modelId: string, capability: keyof ModelInfo['capabilities']): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Validate that the request is valid
|
|
59
|
+
*/
|
|
60
|
+
protected validateRequest(request: TTIRequest): void;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve retry configuration from request
|
|
63
|
+
*/
|
|
64
|
+
protected resolveRetryConfig(request: TTIRequest): Required<RetryOptions> | null;
|
|
65
|
+
/**
|
|
66
|
+
* Calculate delay for a specific retry attempt
|
|
67
|
+
*/
|
|
68
|
+
protected calculateRetryDelay(attempt: number, config: Required<RetryOptions>): number;
|
|
69
|
+
/**
|
|
70
|
+
* Sleep for a specified duration
|
|
71
|
+
*/
|
|
72
|
+
protected sleep(ms: number): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Execute a generation function with retry logic for rate limits
|
|
75
|
+
*/
|
|
76
|
+
protected executeWithRetry<T>(request: TTIRequest, operation: () => Promise<T>, operationName: string): Promise<T>;
|
|
77
|
+
/**
|
|
78
|
+
* Check if an error is a rate limit error (429)
|
|
79
|
+
*/
|
|
80
|
+
protected isRateLimitError(error: Error): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Convert errors to TTIError instances with proper classification
|
|
83
|
+
*/
|
|
84
|
+
protected handleError(error: Error, context?: string): TTIError;
|
|
85
|
+
/**
|
|
86
|
+
* Log messages with provider context
|
|
87
|
+
* Respects global log level setting
|
|
88
|
+
*/
|
|
89
|
+
protected log(level: 'info' | 'warn' | 'error' | 'debug', message: string, meta?: Record<string, unknown>): void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Type guard to check if a request includes reference images
|
|
93
|
+
*/
|
|
94
|
+
export declare function hasReferenceImages(request: TTIRequest): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Check if a region is EU-compliant for GDPR purposes
|
|
97
|
+
*/
|
|
98
|
+
export declare function isEURegion(region: string): boolean;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Base TTI Provider Abstract Class
|
|
4
|
+
*
|
|
5
|
+
* All providers must extend this class and implement the ITTIProvider interface.
|
|
6
|
+
* Provides common functionality for error handling, logging, and validation.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.BaseTTIProvider = exports.CapabilityNotSupportedError = exports.NetworkError = exports.GenerationFailedError = exports.ProviderUnavailableError = exports.QuotaExceededError = exports.InvalidConfigError = exports.TTIError = void 0;
|
|
10
|
+
exports.setLogLevel = setLogLevel;
|
|
11
|
+
exports.getLogLevel = getLogLevel;
|
|
12
|
+
exports.hasReferenceImages = hasReferenceImages;
|
|
13
|
+
exports.isEURegion = isEURegion;
|
|
14
|
+
const types_1 = require("../../../types");
|
|
15
|
+
// ============================================================
|
|
16
|
+
// ERROR CLASSES
|
|
17
|
+
// ============================================================
|
|
18
|
+
class TTIError extends Error {
|
|
19
|
+
constructor(provider, code, message, cause) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.provider = provider;
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.cause = cause;
|
|
24
|
+
this.name = 'TTIError';
|
|
25
|
+
if (Error.captureStackTrace) {
|
|
26
|
+
Error.captureStackTrace(this, TTIError);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
toString() {
|
|
30
|
+
return `[${this.provider}] ${this.code}: ${this.message}${this.cause ? ` (caused by: ${this.cause.message})` : ''}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.TTIError = TTIError;
|
|
34
|
+
class InvalidConfigError extends TTIError {
|
|
35
|
+
constructor(provider, message, cause) {
|
|
36
|
+
super(provider, 'INVALID_CONFIG', message, cause);
|
|
37
|
+
this.name = 'InvalidConfigError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.InvalidConfigError = InvalidConfigError;
|
|
41
|
+
class QuotaExceededError extends TTIError {
|
|
42
|
+
constructor(provider, message, cause) {
|
|
43
|
+
super(provider, 'QUOTA_EXCEEDED', message || 'Provider quota or rate limit exceeded', cause);
|
|
44
|
+
this.name = 'QuotaExceededError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.QuotaExceededError = QuotaExceededError;
|
|
48
|
+
class ProviderUnavailableError extends TTIError {
|
|
49
|
+
constructor(provider, message, cause) {
|
|
50
|
+
super(provider, 'PROVIDER_UNAVAILABLE', message || 'Provider service is temporarily unavailable', cause);
|
|
51
|
+
this.name = 'ProviderUnavailableError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.ProviderUnavailableError = ProviderUnavailableError;
|
|
55
|
+
class GenerationFailedError extends TTIError {
|
|
56
|
+
constructor(provider, message, cause) {
|
|
57
|
+
super(provider, 'GENERATION_FAILED', message, cause);
|
|
58
|
+
this.name = 'GenerationFailedError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.GenerationFailedError = GenerationFailedError;
|
|
62
|
+
class NetworkError extends TTIError {
|
|
63
|
+
constructor(provider, message, cause) {
|
|
64
|
+
super(provider, 'NETWORK_ERROR', message, cause);
|
|
65
|
+
this.name = 'NetworkError';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.NetworkError = NetworkError;
|
|
69
|
+
class CapabilityNotSupportedError extends TTIError {
|
|
70
|
+
constructor(provider, capability, model, cause) {
|
|
71
|
+
super(provider, 'CAPABILITY_NOT_SUPPORTED', `Model '${model}' does not support '${capability}'`, cause);
|
|
72
|
+
this.name = 'CapabilityNotSupportedError';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.CapabilityNotSupportedError = CapabilityNotSupportedError;
|
|
76
|
+
// ============================================================
|
|
77
|
+
// BASE PROVIDER CLASS
|
|
78
|
+
// ============================================================
|
|
79
|
+
/**
|
|
80
|
+
* Global log level for all providers
|
|
81
|
+
* Set via TTI_LOG_LEVEL environment variable or setLogLevel()
|
|
82
|
+
*/
|
|
83
|
+
let globalLogLevel = process.env.TTI_LOG_LEVEL || 'info';
|
|
84
|
+
/**
|
|
85
|
+
* Set the global log level for all TTI providers
|
|
86
|
+
*/
|
|
87
|
+
function setLogLevel(level) {
|
|
88
|
+
globalLogLevel = level;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the current global log level
|
|
92
|
+
*/
|
|
93
|
+
function getLogLevel() {
|
|
94
|
+
return globalLogLevel;
|
|
95
|
+
}
|
|
96
|
+
class BaseTTIProvider {
|
|
97
|
+
constructor(providerName) {
|
|
98
|
+
this.providerName = providerName;
|
|
99
|
+
}
|
|
100
|
+
// ============================================================
|
|
101
|
+
// IMPLEMENTED METHODS
|
|
102
|
+
// ============================================================
|
|
103
|
+
getName() {
|
|
104
|
+
return this.providerName;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get model info by ID
|
|
108
|
+
*/
|
|
109
|
+
getModelInfo(modelId) {
|
|
110
|
+
return this.listModels().find((m) => m.id === modelId);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check if a model supports a specific capability
|
|
114
|
+
*/
|
|
115
|
+
modelSupportsCapability(modelId, capability) {
|
|
116
|
+
const model = this.getModelInfo(modelId);
|
|
117
|
+
if (!model)
|
|
118
|
+
return false;
|
|
119
|
+
return model.capabilities[capability];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validate that the request is valid
|
|
123
|
+
*/
|
|
124
|
+
validateRequest(request) {
|
|
125
|
+
if (!request.prompt || request.prompt.trim().length === 0) {
|
|
126
|
+
throw new InvalidConfigError(this.providerName, 'Prompt cannot be empty');
|
|
127
|
+
}
|
|
128
|
+
// If reference images are provided, validate them
|
|
129
|
+
if (request.referenceImages && request.referenceImages.length > 0) {
|
|
130
|
+
const modelId = request.model || this.getDefaultModel();
|
|
131
|
+
// Check if model supports character consistency
|
|
132
|
+
if (!this.modelSupportsCapability(modelId, 'characterConsistency')) {
|
|
133
|
+
throw new CapabilityNotSupportedError(this.providerName, 'characterConsistency', modelId);
|
|
134
|
+
}
|
|
135
|
+
// Validate subject description is provided
|
|
136
|
+
if (!request.subjectDescription || request.subjectDescription.trim().length === 0) {
|
|
137
|
+
throw new InvalidConfigError(this.providerName, 'subjectDescription is required when using referenceImages');
|
|
138
|
+
}
|
|
139
|
+
// Validate reference images have data
|
|
140
|
+
for (let i = 0; i < request.referenceImages.length; i++) {
|
|
141
|
+
const ref = request.referenceImages[i];
|
|
142
|
+
if (!ref.base64 || ref.base64.trim().length === 0) {
|
|
143
|
+
throw new InvalidConfigError(this.providerName, `Reference image at index ${i} has empty base64 data`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// ============================================================
|
|
149
|
+
// RETRY LOGIC
|
|
150
|
+
// ============================================================
|
|
151
|
+
/**
|
|
152
|
+
* Resolve retry configuration from request
|
|
153
|
+
*/
|
|
154
|
+
resolveRetryConfig(request) {
|
|
155
|
+
const retryOption = request.retry;
|
|
156
|
+
// Explicit disable
|
|
157
|
+
if (retryOption === false) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
// Default (undefined) or explicit true: use defaults
|
|
161
|
+
if (retryOption === undefined || retryOption === true) {
|
|
162
|
+
return { ...types_1.DEFAULT_RETRY_OPTIONS };
|
|
163
|
+
}
|
|
164
|
+
// Custom configuration: merge with defaults
|
|
165
|
+
return {
|
|
166
|
+
maxRetries: retryOption.maxRetries ?? types_1.DEFAULT_RETRY_OPTIONS.maxRetries,
|
|
167
|
+
delayMs: retryOption.delayMs ?? types_1.DEFAULT_RETRY_OPTIONS.delayMs,
|
|
168
|
+
incrementalBackoff: retryOption.incrementalBackoff ?? types_1.DEFAULT_RETRY_OPTIONS.incrementalBackoff,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Calculate delay for a specific retry attempt
|
|
173
|
+
*/
|
|
174
|
+
calculateRetryDelay(attempt, config) {
|
|
175
|
+
if (config.incrementalBackoff) {
|
|
176
|
+
// Incremental: 1s, 2s, 3s, ...
|
|
177
|
+
return config.delayMs * attempt;
|
|
178
|
+
}
|
|
179
|
+
// Static: always same delay
|
|
180
|
+
return config.delayMs;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Sleep for a specified duration
|
|
184
|
+
*/
|
|
185
|
+
sleep(ms) {
|
|
186
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Execute a generation function with retry logic for rate limits
|
|
190
|
+
*/
|
|
191
|
+
async executeWithRetry(request, operation, operationName) {
|
|
192
|
+
const retryConfig = this.resolveRetryConfig(request);
|
|
193
|
+
// No retry configured
|
|
194
|
+
if (!retryConfig) {
|
|
195
|
+
return operation();
|
|
196
|
+
}
|
|
197
|
+
let lastError = null;
|
|
198
|
+
const maxAttempts = 1 + retryConfig.maxRetries; // initial + retries
|
|
199
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
200
|
+
try {
|
|
201
|
+
return await operation();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
lastError = error;
|
|
205
|
+
// Check if this is a rate limit error (429)
|
|
206
|
+
const isRateLimitError = this.isRateLimitError(error);
|
|
207
|
+
// Only retry on rate limit errors
|
|
208
|
+
if (!isRateLimitError) {
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
// Check if we have retries left
|
|
212
|
+
if (attempt < maxAttempts) {
|
|
213
|
+
const delay = this.calculateRetryDelay(attempt, retryConfig);
|
|
214
|
+
this.log('warn', `Rate limit hit during ${operationName}. Retry ${attempt}/${retryConfig.maxRetries} in ${delay}ms...`, { attempt, maxRetries: retryConfig.maxRetries, delayMs: delay });
|
|
215
|
+
await this.sleep(delay);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// All retries exhausted
|
|
220
|
+
throw lastError;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if an error is a rate limit error (429)
|
|
224
|
+
*/
|
|
225
|
+
isRateLimitError(error) {
|
|
226
|
+
const message = error.message.toLowerCase();
|
|
227
|
+
return (message.includes('429') ||
|
|
228
|
+
message.includes('rate limit') ||
|
|
229
|
+
message.includes('quota exceeded') ||
|
|
230
|
+
message.includes('too many requests') ||
|
|
231
|
+
message.includes('resource exhausted'));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Convert errors to TTIError instances with proper classification
|
|
235
|
+
*/
|
|
236
|
+
handleError(error, context) {
|
|
237
|
+
if (error instanceof TTIError) {
|
|
238
|
+
return error;
|
|
239
|
+
}
|
|
240
|
+
const errorMessage = error.message.toLowerCase();
|
|
241
|
+
if (errorMessage.includes('401') || errorMessage.includes('403')) {
|
|
242
|
+
return new InvalidConfigError(this.providerName, `Authentication failed${context ? `: ${context}` : ''}`, error);
|
|
243
|
+
}
|
|
244
|
+
if (errorMessage.includes('429')) {
|
|
245
|
+
return new QuotaExceededError(this.providerName, `Rate limit exceeded${context ? `: ${context}` : ''}`, error);
|
|
246
|
+
}
|
|
247
|
+
if (errorMessage.includes('503') ||
|
|
248
|
+
errorMessage.includes('504') ||
|
|
249
|
+
errorMessage.includes('502')) {
|
|
250
|
+
return new ProviderUnavailableError(this.providerName, `Service temporarily unavailable${context ? `: ${context}` : ''}`, error);
|
|
251
|
+
}
|
|
252
|
+
if (errorMessage.includes('timeout') ||
|
|
253
|
+
errorMessage.includes('econnrefused') ||
|
|
254
|
+
errorMessage.includes('enotfound')) {
|
|
255
|
+
return new NetworkError(this.providerName, `Network error${context ? `: ${context}` : ''}`, error);
|
|
256
|
+
}
|
|
257
|
+
return new GenerationFailedError(this.providerName, `Generation failed${context ? `: ${context}` : ''}: ${error.message}`, error);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Log messages with provider context
|
|
261
|
+
* Respects global log level setting
|
|
262
|
+
*/
|
|
263
|
+
log(level, message, meta) {
|
|
264
|
+
// Check if we should log this level
|
|
265
|
+
const currentPriority = types_1.LOG_LEVEL_PRIORITY[level];
|
|
266
|
+
const minPriority = types_1.LOG_LEVEL_PRIORITY[globalLogLevel];
|
|
267
|
+
if (currentPriority < minPriority) {
|
|
268
|
+
return; // Skip logging if below minimum level
|
|
269
|
+
}
|
|
270
|
+
const timestamp = new Date().toISOString();
|
|
271
|
+
const prefix = `[${timestamp}] [${this.providerName.toUpperCase()}] [${level.toUpperCase()}]`;
|
|
272
|
+
if (meta) {
|
|
273
|
+
console[level](prefix, message, meta);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console[level](prefix, message);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
exports.BaseTTIProvider = BaseTTIProvider;
|
|
281
|
+
// ============================================================
|
|
282
|
+
// HELPER FUNCTIONS
|
|
283
|
+
// ============================================================
|
|
284
|
+
/**
|
|
285
|
+
* Type guard to check if a request includes reference images
|
|
286
|
+
*/
|
|
287
|
+
function hasReferenceImages(request) {
|
|
288
|
+
return (request.referenceImages !== undefined &&
|
|
289
|
+
Array.isArray(request.referenceImages) &&
|
|
290
|
+
request.referenceImages.length > 0);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if a region is EU-compliant for GDPR purposes
|
|
294
|
+
*/
|
|
295
|
+
function isEURegion(region) {
|
|
296
|
+
const euRegions = [
|
|
297
|
+
'europe-west1',
|
|
298
|
+
'europe-west2',
|
|
299
|
+
'europe-west3',
|
|
300
|
+
'europe-west4',
|
|
301
|
+
'europe-west9',
|
|
302
|
+
'europe-north1',
|
|
303
|
+
'europe-central2',
|
|
304
|
+
];
|
|
305
|
+
return euRegions.includes(region);
|
|
306
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eden AI TTI Provider
|
|
3
|
+
*
|
|
4
|
+
* Aggregator service that provides access to multiple AI providers
|
|
5
|
+
* through a unified API.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.edenai.co/
|
|
8
|
+
* @see https://docs.edenai.co/docs/pricing
|
|
9
|
+
*/
|
|
10
|
+
import { TTIRequest, TTIResponse, ModelInfo } from '../../../types';
|
|
11
|
+
import { BaseTTIProvider } from './base-tti-provider';
|
|
12
|
+
interface EdenAIConfig {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
apiUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class EdenAIProvider extends BaseTTIProvider {
|
|
17
|
+
private config;
|
|
18
|
+
private readonly apiUrl;
|
|
19
|
+
constructor(config?: Partial<EdenAIConfig>);
|
|
20
|
+
getDisplayName(): string;
|
|
21
|
+
listModels(): ModelInfo[];
|
|
22
|
+
getDefaultModel(): string;
|
|
23
|
+
generate(request: TTIRequest): Promise<TTIResponse>;
|
|
24
|
+
private executeGeneration;
|
|
25
|
+
private aspectRatioToSize;
|
|
26
|
+
private processResponse;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Eden AI TTI Provider
|
|
4
|
+
*
|
|
5
|
+
* Aggregator service that provides access to multiple AI providers
|
|
6
|
+
* through a unified API.
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.edenai.co/
|
|
9
|
+
* @see https://docs.edenai.co/docs/pricing
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EdenAIProvider = void 0;
|
|
13
|
+
const types_1 = require("../../../types");
|
|
14
|
+
const base_tti_provider_1 = require("./base-tti-provider");
|
|
15
|
+
// ============================================================
|
|
16
|
+
// MODEL DEFINITIONS
|
|
17
|
+
// ============================================================
|
|
18
|
+
const EDENAI_MODELS = [
|
|
19
|
+
{
|
|
20
|
+
id: 'openai',
|
|
21
|
+
displayName: 'OpenAI DALL-E',
|
|
22
|
+
capabilities: {
|
|
23
|
+
textToImage: true,
|
|
24
|
+
characterConsistency: false,
|
|
25
|
+
imageEditing: false,
|
|
26
|
+
maxImagesPerRequest: 4,
|
|
27
|
+
},
|
|
28
|
+
pricingUrl: 'https://www.edenai.co/pricing',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'stabilityai',
|
|
32
|
+
displayName: 'Stability AI',
|
|
33
|
+
capabilities: {
|
|
34
|
+
textToImage: true,
|
|
35
|
+
characterConsistency: false,
|
|
36
|
+
imageEditing: false,
|
|
37
|
+
maxImagesPerRequest: 4,
|
|
38
|
+
},
|
|
39
|
+
pricingUrl: 'https://www.edenai.co/pricing',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'replicate',
|
|
43
|
+
displayName: 'Replicate',
|
|
44
|
+
capabilities: {
|
|
45
|
+
textToImage: true,
|
|
46
|
+
characterConsistency: false,
|
|
47
|
+
imageEditing: false,
|
|
48
|
+
maxImagesPerRequest: 4,
|
|
49
|
+
},
|
|
50
|
+
pricingUrl: 'https://www.edenai.co/pricing',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
// ============================================================
|
|
54
|
+
// PROVIDER IMPLEMENTATION
|
|
55
|
+
// ============================================================
|
|
56
|
+
class EdenAIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
57
|
+
constructor(config) {
|
|
58
|
+
super(types_1.TTIProvider.EDENAI);
|
|
59
|
+
this.config = {
|
|
60
|
+
apiKey: config?.apiKey || process.env.EDENAI_API_KEY || '',
|
|
61
|
+
apiUrl: config?.apiUrl,
|
|
62
|
+
};
|
|
63
|
+
this.apiUrl =
|
|
64
|
+
this.config.apiUrl || 'https://api.edenai.run/v2/image/generation';
|
|
65
|
+
if (!this.config.apiKey) {
|
|
66
|
+
throw new base_tti_provider_1.InvalidConfigError(this.providerName, 'EdenAI API key is required (EDENAI_API_KEY)');
|
|
67
|
+
}
|
|
68
|
+
this.log('info', 'Eden AI Provider initialized');
|
|
69
|
+
}
|
|
70
|
+
// ============================================================
|
|
71
|
+
// ITTIProvider IMPLEMENTATION
|
|
72
|
+
// ============================================================
|
|
73
|
+
getDisplayName() {
|
|
74
|
+
return 'Eden AI';
|
|
75
|
+
}
|
|
76
|
+
listModels() {
|
|
77
|
+
return EDENAI_MODELS;
|
|
78
|
+
}
|
|
79
|
+
getDefaultModel() {
|
|
80
|
+
return 'openai';
|
|
81
|
+
}
|
|
82
|
+
async generate(request) {
|
|
83
|
+
this.validateRequest(request);
|
|
84
|
+
return this.executeWithRetry(request, () => this.executeGeneration(request), 'EdenAI API call');
|
|
85
|
+
}
|
|
86
|
+
async executeGeneration(request) {
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
const modelId = request.model || this.getDefaultModel();
|
|
89
|
+
const body = {
|
|
90
|
+
providers: modelId,
|
|
91
|
+
text: request.prompt,
|
|
92
|
+
resolution: request.aspectRatio ? this.aspectRatioToSize(request.aspectRatio) : '1024x1024',
|
|
93
|
+
num_images: request.n || 1,
|
|
94
|
+
};
|
|
95
|
+
this.log('debug', 'Generating image with EdenAI', {
|
|
96
|
+
provider: modelId,
|
|
97
|
+
size: body.resolution,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(this.apiUrl, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const errorText = await response.text();
|
|
110
|
+
throw new Error(`EdenAI API error (${response.status}): ${errorText}`);
|
|
111
|
+
}
|
|
112
|
+
const data = (await response.json());
|
|
113
|
+
this.log('debug', 'Raw EdenAI response', { data: JSON.stringify(data) });
|
|
114
|
+
const duration = Date.now() - startTime;
|
|
115
|
+
return this.processResponse(data, modelId, duration);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw this.handleError(error, 'during EdenAI API call');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ============================================================
|
|
122
|
+
// PRIVATE METHODS
|
|
123
|
+
// ============================================================
|
|
124
|
+
aspectRatioToSize(aspectRatio) {
|
|
125
|
+
const mapping = {
|
|
126
|
+
'1:1': '1024x1024',
|
|
127
|
+
'16:9': '1792x1024',
|
|
128
|
+
'9:16': '1024x1792',
|
|
129
|
+
'4:3': '1024x768',
|
|
130
|
+
'3:4': '768x1024',
|
|
131
|
+
};
|
|
132
|
+
return mapping[aspectRatio] || '1024x1024';
|
|
133
|
+
}
|
|
134
|
+
processResponse(data, provider, duration) {
|
|
135
|
+
const responseKeys = Object.keys(data);
|
|
136
|
+
const matchedKey = responseKeys.find((key) => key.startsWith(provider));
|
|
137
|
+
const providerData = matchedKey ? data[matchedKey] : undefined;
|
|
138
|
+
if (!providerData) {
|
|
139
|
+
this.log('error', `Provider data not found for ${provider}. Available keys: ${responseKeys.join(', ')}`);
|
|
140
|
+
throw new base_tti_provider_1.GenerationFailedError(this.providerName, `No data returned for provider: ${provider}`);
|
|
141
|
+
}
|
|
142
|
+
if (providerData.status === 'fail' || providerData.error) {
|
|
143
|
+
throw new base_tti_provider_1.GenerationFailedError(this.providerName, providerData.error || 'Unknown error from provider');
|
|
144
|
+
}
|
|
145
|
+
const items = providerData.items || [];
|
|
146
|
+
const images = [];
|
|
147
|
+
for (const item of items) {
|
|
148
|
+
if (item.image) {
|
|
149
|
+
images.push({ base64: item.image, contentType: 'image/png' });
|
|
150
|
+
}
|
|
151
|
+
else if (item.image_resource_url) {
|
|
152
|
+
images.push({ url: item.image_resource_url });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (images.length === 0) {
|
|
156
|
+
throw new base_tti_provider_1.GenerationFailedError(this.providerName, 'No images returned in response');
|
|
157
|
+
}
|
|
158
|
+
const usage = {
|
|
159
|
+
imagesGenerated: images.length,
|
|
160
|
+
modelId: provider,
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
images,
|
|
164
|
+
metadata: {
|
|
165
|
+
provider: this.providerName,
|
|
166
|
+
model: provider,
|
|
167
|
+
duration,
|
|
168
|
+
},
|
|
169
|
+
usage,
|
|
170
|
+
// Eden AI returns actual cost from provider!
|
|
171
|
+
billing: providerData.cost
|
|
172
|
+
? {
|
|
173
|
+
cost: providerData.cost,
|
|
174
|
+
currency: 'USD',
|
|
175
|
+
source: 'provider',
|
|
176
|
+
}
|
|
177
|
+
: undefined,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.EdenAIProvider = EdenAIProvider;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Cloud TTI Provider
|
|
3
|
+
*
|
|
4
|
+
* Unified provider for Google Cloud's image generation services:
|
|
5
|
+
* - Imagen 3 (imagegeneration@006) - High quality text-to-image
|
|
6
|
+
* - Gemini 2.5 Flash Image - Text-to-image with character consistency
|
|
7
|
+
*
|
|
8
|
+
* All requests go through Google Cloud (Vertex AI) with proper DPA.
|
|
9
|
+
* EU-compliant when using EU regions.
|
|
10
|
+
*
|
|
11
|
+
* @see https://cloud.google.com/vertex-ai/generative-ai/pricing
|
|
12
|
+
* @see https://cloud.google.com/terms/data-processing-addendum
|
|
13
|
+
*/
|
|
14
|
+
import { TTIRequest, TTIResponse, ModelInfo, GoogleCloudRegion } from '../../../types';
|
|
15
|
+
import { BaseTTIProvider } from './base-tti-provider';
|
|
16
|
+
interface GoogleCloudConfig {
|
|
17
|
+
/** Google Cloud Project ID */
|
|
18
|
+
projectId: string;
|
|
19
|
+
/** Default region for requests */
|
|
20
|
+
region: GoogleCloudRegion;
|
|
21
|
+
/** Path to service account JSON file */
|
|
22
|
+
keyFilename?: string;
|
|
23
|
+
/** Service account credentials object (alternative to keyFilename) */
|
|
24
|
+
credentials?: {
|
|
25
|
+
client_email: string;
|
|
26
|
+
private_key: string;
|
|
27
|
+
project_id?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export declare class GoogleCloudTTIProvider extends BaseTTIProvider {
|
|
31
|
+
private config;
|
|
32
|
+
private lastUsedRegion;
|
|
33
|
+
private aiplatformClient;
|
|
34
|
+
private genaiClient;
|
|
35
|
+
constructor(config?: Partial<GoogleCloudConfig>);
|
|
36
|
+
getDisplayName(): string;
|
|
37
|
+
listModels(): ModelInfo[];
|
|
38
|
+
getDefaultModel(): string;
|
|
39
|
+
generate(request: TTIRequest): Promise<TTIResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* Get the configured region
|
|
42
|
+
*/
|
|
43
|
+
getRegion(): GoogleCloudRegion;
|
|
44
|
+
/**
|
|
45
|
+
* Check if the configured region is hosted in the EU
|
|
46
|
+
*/
|
|
47
|
+
isEURegion(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Get the effective region for a model, considering availability
|
|
50
|
+
*/
|
|
51
|
+
private getEffectiveRegion;
|
|
52
|
+
private generateWithImagen;
|
|
53
|
+
private getAiplatformClient;
|
|
54
|
+
private processImagenResponse;
|
|
55
|
+
private generateWithGemini;
|
|
56
|
+
private getGenaiClient;
|
|
57
|
+
private buildCharacterConsistencyPrompt;
|
|
58
|
+
private processGeminiResponse;
|
|
59
|
+
}
|
|
60
|
+
export {};
|